<rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0"><channel><title>Hacker News Personal Blogs 2023 | All Blog Posts</title><link>https://hn-blogs.kronis.dev/feed.xml</link><description>A collection of blog posts from users of Hacker News, based on RSS feeds.</description><language>en-US</language><lastBuildDate>Fri, 10 Jul 2026 04:03:21 GMT</lastBuildDate><generator>rfeed v1.1.1</generator><docs>https://github.com/svpino/rfeed/blob/master/README.md</docs><item><title>An Ordinary Disaster in 2024</title><link>https://bowendwelle.substack.com/p/an-ordinary-disaster-in-2024</link><description>Prepare yourself, create options&amp;#8212;or just walk.</description><author>An Ordinary Disaster</author><pubDate>Sun, 31 Dec 2023 21:27:04 GMT</pubDate><guid isPermaLink="true">https://bowendwelle.substack.com/p/an-ordinary-disaster-in-2024</guid></item><item><title>2023 in review</title><link>https://callmeo.live/blog/2023-in-review/</link><description>&lt;p&gt;2023 was certainly &lt;em&gt;a year&lt;/em&gt;. Our Prime Minister got fined for not wearing a seatbelt, Paul O&amp;rsquo;Grady&amp;rsquo;s passing left millions of dog lovers and radio fans mourning, and shortly after some bloke got crowned king in a ceremony full of high-definition horse manure. The long-running series of Governmental cock-ups continued as they lost more seats and dithered from crisis to crisis, the usual. Also the F-22 got it&amp;rsquo;s first air-to-air kill against a balloon of all things.&lt;/p&gt;
&lt;p&gt;At the halfway point we all became submarine experts, Barbenheimer took the internet by storm until corporations tried to be funny and Hollywood writers went on strike probably in retaliation. The Beatles released their final song until the next one comes along, David Tennant returned briefly to Doctor Who, and I couldn&amp;rsquo;t think of any news from December to put here (E3 being officially over feels too lacklustre, even for this).&lt;/p&gt;
&lt;p&gt;Reddit, after going public, decided to axe it&amp;rsquo;s free API promise which killed off all the good mobile apps and led to an exodus from the site – but in their hopes to get the coveted &lt;em&gt;digital service cock-up of the year&lt;/em&gt; award they were swiftly beaten by Elon Musk. In a dramatic move he finally renamed Twitter to &lt;em&gt;Formerly Twitter&lt;/em&gt;, and in a desperate attempt to keep money flowing in told advertisers to go fuck themselves. 🤔&lt;/p&gt;
&lt;p&gt;Google, deciding that having the largest market share of internet browsers, search engines, and phone operating systems wasn&amp;rsquo;t good enough, tried to wrangle more power from the web with a dodgy DRM system they realised nobody actually wanted. They also tried blocking ad blockers on YouTube. Ad blockers just got better.&lt;/p&gt;
&lt;p&gt;All throughout this the continued push in AI brought forth major strides in image and text generation, yet as a Language Model I am unable to discuss such a topic in the form of a &amp;lsquo;quirky and engaging blog post&amp;rsquo;.&lt;/p&gt;
&lt;p&gt;Myself? Same old. Started some naff blog, bought a Steam Deck, went through the &lt;em&gt;install Linux on a ThinkPad&lt;/em&gt; rite of passage, said goodbye to my old desktop as I built a new one (RIP R9 390). Finally caught Covid, one tooth removed, went on antidepressants twice, GPs proved to be as useless as ever, life goes on xx&lt;/p&gt;
&lt;h2 id="the-stats"&gt;The stats&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;1 new website&lt;/li&gt;
&lt;li&gt;14 blog posts&lt;/li&gt;
&lt;li&gt;5 years of sobriety&lt;/li&gt;
&lt;li&gt;12 books read&lt;/li&gt;
&lt;li&gt;20 games I purchased on sale and never played&lt;/li&gt;
&lt;li&gt;1 extremely sleep-deprived article pitch to The Guardian which they didn&amp;rsquo;t respond to because &lt;del&gt;the liberal agenda fears me&lt;/del&gt; &lt;del&gt;it had no typos&lt;/del&gt; it was bad.&lt;/li&gt;
&lt;li&gt;11 times my gay cat caught something (not including fleas)&lt;/li&gt;
&lt;li&gt;Word of the year: Meandering&lt;/li&gt;
&lt;li&gt;Favourite game: Super Metroid&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="blog-posts-wot-didnt-make-the-cut"&gt;Blog posts wot didn&amp;rsquo;t make the cut&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Recapturing imagination&lt;/strong&gt; – Lamenting the loss of my childhood imagination, I went on a journey to seek it back. I fizzled out after a few weeks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;When search engines don&amp;rsquo;t search [what you want]&lt;/strong&gt; – A quick dive into search engines being useless on a whim. And advertisements.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Learning to love Linux&lt;/strong&gt; – Repetitive. A lot of &lt;em&gt;things worked until they didn&amp;rsquo;t&lt;/em&gt; until I tried Kubuntu.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[something about declining political standards]&lt;/strong&gt; – Everyone thinks they&amp;rsquo;re a pundit.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Why are lights so bright?&lt;/strong&gt; – Every device has a retina burning LED, adding resistors costs money, and I simply didn&amp;rsquo;t get around to fleshing it out&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;People to send straight to hell&lt;/strong&gt; – &amp;ldquo;Satirical&amp;rdquo; list of minor actions &amp;amp; behaviours that ought to immediately sent people to eternal damnation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That about wraps it up, ignoring everything I missed. Hopefully 2024 is better xoxo&lt;/p&gt;
&lt;p&gt;Olive&lt;/p&gt;</description><author>callmeolive</author><pubDate>Sun, 31 Dec 2023 21:15:00 GMT</pubDate><guid isPermaLink="true">https://callmeo.live/blog/2023-in-review/</guid></item><item><title>New Year's Resolutions 2024</title><link>https://oo.svbtle.com/new-year-s-resolutions-2024</link><description>&lt;h2 id="resolutions_2"&gt;Resolutions &lt;a class="head_anchor" href="#resolutions_2"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;make connections 🧑🏼‍❤️‍🧑🏻&lt;/li&gt;
&lt;li&gt;get better 💝&lt;/li&gt;
&lt;li&gt;be better 🌁&lt;/li&gt;
&lt;li&gt;explore ways to self-expression 🌼&lt;/li&gt;
&lt;li&gt;treat every day like a new day 🌄&lt;/li&gt;
&lt;li&gt;celebrate success, learn from mistakes 🫙&lt;/li&gt;
&lt;li&gt;accept 💨&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="backstory_2"&gt;Backstory &lt;a class="head_anchor" href="#backstory_2"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Hi, it’s been a while - but I’ve most certainly never stopped thinking about you.&lt;/p&gt;

&lt;p&gt;Last year when writing my resolutions I was quite behind and almost forgot since I had quite some trip around that time.&lt;/p&gt;

&lt;p&gt;Firstly back then I just quit my job and went for a trip, first to Canary Islands and later to Berlin, where I’ve just arrived a day prior. Canary Islands were quite tough, I was just exhausted from finishing my previous job and going anywhere. The back then friend was quite hard to deal with and if Lanzarote wasn’t that beautiful I would have almost had a breakdown otherwise.&lt;/p&gt;

&lt;p&gt;Berlin was great, I’ve met sometimes several people per day, visited to concerts and museums.&lt;/p&gt;

&lt;p&gt;Around 14. January I started my new job, at an office in Vienna for the first two weeks. …&lt;/p&gt;

&lt;p&gt;Anywho. I think I’ll give another recap of my 2023 in another posting. What I wanted to say is I didn’t spent a lot of time writing my resolutions a year ago.&lt;/p&gt;

&lt;p&gt;Today is different, I’m at my apartment in Portugal. Unfortunately my friends who wanted to join just canceled for tonight, I’ve prepared Pizza and planned for a nice evening together. Let’s use this as an opportunity to explore something new or not I’ll see.&lt;/p&gt;

&lt;p&gt;I’ve been thinking about these resolutions for quite some time. How can I formulate them in a for me meaningful and helpful matter. Most recently I thought of maybe shifting the idea of resolutions a bit and rather do a more OKR like format. For that I’ll write my main theme for the year and then try to see what’s next on a quarterly basis.&lt;/p&gt;

&lt;p&gt;Thank you for being there. xoxo&lt;/p&gt;</description><author>Syd Norpol</author><pubDate>Sun, 31 Dec 2023 19:38:53 GMT</pubDate><guid isPermaLink="true">https://oo.svbtle.com/new-year-s-resolutions-2024</guid></item><item><title>Some nuggets of wisdom from Charlie Munger</title><link>https://blog.vslira.net/2023/12/some-nuggets-of-wisdom-from-charlie.html</link><description>&lt;br /&gt;&lt;br /&gt; Stripe Press recently released a new edition of Poor Charlie's Almanack, exactly a week after Munger sadly passed away. I had preordered the book and picked it up a few weeks after release. There are many good quips and general good advice in there and I will share the most interesting tidbits to me.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;First, the "mental model checklist". Charlie Munger is famous for his "mental models" framework and many outlets - books, websites, content writers - push mental models either because they believe in the framework or, more cynically, because people buy it trying to be more like Munger (that is, rich). I don't have a lot to add here, other than that when I read about this "mental models" frameworks, it never came to me the idea of a literal checklist. But that's how Munger explains his framework: a literal checklist.&lt;/div&gt;&lt;div&gt;You go one by one, see which ones apply and - crucially important, according to him - when many apply (as in most real life cases) one has to figure out how these systems interact. I don't know if I will adopt this practice, but whenever anyone refers to "mental models" to me in the future, I'll make a note to try to use them as a checklist instead of just learning them and hoping my subconscious mind does the job of pulling the right ones at the right time.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;To be honest, if I could risk a lesson that Munger may or may not have expressed himself, I'd say he's very critical of relying on your subconscious mind to make decisions for you. One should be aware of how one thinks when making decisions.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The second lesson that I took a liking for was his view of a little unfairness being more fair than fairness in all interactions. Specifically, he mentions that in the U.S. Navy a captain's career is over if his ship turns around. It doesn't matter if it was his fault: his career is over. This may not be fair to the individual captain, but keeps every other one on their toes and the increased tension saves many lives as a result. Sometimes I wonder if this, intentionally or not, is happening in high-performance but high-stress careers, famous for being unforgiving. Maybe it's better this way?&lt;/div&gt;</description><author>vslira's blog</author><pubDate>Sun, 31 Dec 2023 19:18:04 GMT</pubDate><guid isPermaLink="true">https://blog.vslira.net/2023/12/some-nuggets-of-wisdom-from-charlie.html</guid></item><item><title>avatar.png</title><link>https://tuckersiemens.com/posts/avatar-png/</link><description>&lt;p&gt;No, not that Avatar. And not the other one either. This post is about
&lt;code&gt;avatar.png&lt;/code&gt;, a handful of lines of PHP that have inspired me for a long time.&lt;/p&gt;
&lt;p&gt;Around 2011 or 2012 a friend of mine, &lt;a href="https://andrew.kvalhe.im/"&gt;Andrew Kvalheim&lt;/a&gt;, blew
my mind when he made his Skype profile picture display the IP address of the
computer I was using. It might have looked a bit like this.&lt;/p&gt;</description><author>Reilly Tucker Siemens</author><pubDate>Sun, 31 Dec 2023 14:56:00 GMT</pubDate><guid isPermaLink="true">https://tuckersiemens.com/posts/avatar-png/</guid></item><item><title>Meet the InfoSec Planet</title><link>https://blog.ovalerio.net/archives/2717</link><description>If you are a frequent reader of this blog, you might already know that I created a small tool to generate a simple webpage plus an RSS feed, from the content of multiple other RSS sources, called worker-planet. This type of tool is often known as a “planet”: In online media a planet is a [&amp;#8230;]</description><author>Gonçalo Valério</author><pubDate>Sun, 31 Dec 2023 14:13:04 GMT</pubDate><guid isPermaLink="true">https://blog.ovalerio.net/archives/2717</guid></item><item><title>Whale Song - Some Background</title><link>https://blog.herlein.com/post/whale-song/</link><description>&lt;p&gt;Recent press about an actual conversation with a whale was preceeded by ground-breaking work by &lt;a href="https://www.linkedin.com/in/michelle-e-h-fournet-she-her-51033b85/"&gt;Dr. Michelle Fournet&lt;/a&gt; and profiled in the fantastic documentary &lt;a href="https://tv.apple.com/us/movie/fathom/umc.cmc.5dba56sgwst50iuh5h9uqpdsq"&gt;Fathom&lt;/a&gt; on AppleTV.  This is a MUST WATCH if you love the ocean - or humanity!&lt;/p&gt;</description><author>Greg Herlein</author><pubDate>Sun, 31 Dec 2023 10:00:01 GMT</pubDate><guid isPermaLink="true">https://blog.herlein.com/post/whale-song/</guid></item><item><title>A small reprieve from poorly executed ideas</title><link>https://utf9k.net/blog/scooter-disliker/</link><description>Maybe one sector's recession is another person's happy place?</description><author>utf9k</author><pubDate>Sun, 31 Dec 2023 04:20:00 GMT</pubDate><guid isPermaLink="true">https://utf9k.net/blog/scooter-disliker/</guid></item><item><title>Summary : Little Bets by Peter Sims</title><link>https://www.chestergrant.com/summary-little-bets-by-peter-sims</link><description>&lt;div class="posthaven-post-body"&gt;&lt;p&gt;&lt;/p&gt;&lt;center&gt;        &lt;div class="posthaven-gallery" id="posthaven_gallery[2093305]"&gt;
                  &lt;p class="posthaven-file posthaven-file-image posthaven-file-state-processed"&gt;
          &lt;img class="posthaven-gallery-image" src="https://phaven-prod.s3.amazonaws.com/files/image_part/asset/3126181/XN_qd7Rc2rte5AOMd9KUCQW2DBg/medium_cover.png" /&gt;
        &lt;/p&gt;

        &lt;/div&gt;
&lt;/center&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;1. When success is the only acceptable outcome, Little Bets advocates a bold and radical approach in which failure is good,&lt;/p&gt;&lt;p&gt;2. I had worked before then as a venture capital investor, and in that work, I had learned that most successful entrepreneurs don’t begin with brilliant ideas—they discover them.&lt;/p&gt;&lt;p&gt;&lt;b&gt;3. These creators use experimental, iterative, trial-and-error approaches to gradually build up to breakthroughs. Experimental innovators must be persistent and willing to accept failure and setbacks as they work toward their goals&lt;/b&gt;.&lt;/p&gt;&lt;p&gt;4. Little Bets is based on the proposition that we can use a lot of little bets and certain creative methods to identify possibilities and build up to great outcomes. At the core of this experimental approach, little bets are concrete actions taken to discover, test, and develop ideas that are achievable and affordable.&lt;/p&gt;&lt;p&gt;5. They begin as creative possibilities that get iterated and refined over time, and they are particularly valuable when trying to navigate amid uncertainty, create something new, or attend to open-ended problems.&lt;/p&gt;&lt;p&gt;6. When we can’t know what’s going to happen, little bets help us learn about the factors that can’t be understood beforehand. The important thing to remember is that while prodigies are exceptionally rare, anyone can use little bets to unlock creative ideas.&lt;/p&gt;&lt;p&gt;7. Fundamental to the little bets approach is that we: &lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Experiment: Learn by doing. Fail quickly to learn fast. Develop experiments and prototypes to gather insights, identify problems, and build up to creative ideas, like Beethoven did in order to discover new musical styles and forms. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Play: A playful, improvisational, and humorous atmosphere quiets our inhibitions when ideas are incubating or newly hatched, and prevents creative ideas from being snuffed out or prematurely judged. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Immerse: Take time to get out into the world to gather fresh ideas and insights, in order to understand deeper human motivations and desires, and absorb how things work from the ground up. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Define: Use insights gathered throughout the process to define specific problems and needs before solving them, just as the Google founders did when they realized that their library search algorithm could address a much larger problem. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reorient: Be flexible in pursuit of larger goals and aspirations, making making good use of small wins to make necessary pivots and chart the course to completion. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Iterate: Repeat, refine, and test frequently armed with better insights, information, and assumptions as time goes on, as Chris Rock does to perfect his act.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;b&gt;8. Two fundamental advantages of the little bets approach are highlighted in the research of Professor Saras Sarasvathy: that it enables us to focus on what we can afford to lose rather than make assumptions about how much we can expect to gain, and that it facilitates the development of means as we progress with an idea.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;9. Seasoned entrepreneurs, she emphasizes, will tend to determine in advance what they are willing to lose, rather than calculating expected gains.&lt;/p&gt;&lt;p&gt;10. Of course the subject of affordable losses highlights a key issue with the little bets approach—it inevitably involves failure.&lt;/p&gt;&lt;p&gt;11. In almost any attempt to create, failure, and often a good deal of it, is to be expected.&lt;/p&gt;&lt;p&gt;12. Hewlett’s approach to identifying new opportunities using small bets did not come without numerous failures. In 1971, HP featured over 1,600 products in its catalog, none of which averaged sales of more than ten units per day, according to Chuck House. In fact, Hewlett estimated that roughly only six out of every 100 new HP products would become breakout successes.&lt;/p&gt;&lt;p&gt;13. By advocating the little bets approach, I am in no way arguing against bold ambition. Ambitious (dare I employ the overused word audacious) goals are essential.&lt;/p&gt;&lt;p&gt;14. A big vision provides the direction and inspiration through which to channel aspirations and ideas. But one of the most important lessons of the study of experimental innovators is that they are not rigid in pursuit of that vision, and that they persevere through failures, often many of them.&lt;/p&gt;&lt;p&gt;15. Central to Pixar’s success in fostering this growth mind-set through the ranks is the company’s attitude about failure. Pixar’s managers see a host of failures, false starts, and problems as the modus operandi for developing their films. In fact, when Ed Catmull sums up Pixar’s creative process, he describes it as going from suck to nonsuck.&lt;/p&gt;&lt;p&gt;16. Pixar film ideas begin on rough storyboards that suck until they work through thousands of problems throughout the process in order to take films from suck to nonsuck.&lt;/p&gt;&lt;p&gt;17. Of course, just failing is not the key; the key is to be systematically learning from failures.&lt;/p&gt;&lt;p&gt;18. One of the methods that can be most helpful in achieving this balance, in order to embrace the learning potential of failure, is prototyping. What the creation of low cost, rough prototypes makes possible is failing quickly in order to learn fast.&lt;/p&gt;&lt;p&gt;&lt;b&gt;19. Failing quickly to learn fast is also a central operating principle for seasoned entrepreneurs who routinely describe their approach as failing forward. That is, entrepreneurs push ideas into the market as quickly as possible in order to learn from mistakes and failures that will point the way forward.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;20. Novelist Anne Lamott believes that every good writer writes what she calls shitty first drafts.&lt;/p&gt;&lt;p&gt;&lt;b&gt;21. The only way I can get anything written at all is to write really, really shitty first drafts,” Lamott writes in Bird by Bird. Just get it down on paper, she recommends. Write like a child, whatever comes to your mind. “All good writers write them. This is how they end up with good second drafts and terrific third drafts.”&lt;/b&gt;&lt;/p&gt;&lt;p&gt;22. Ed Catmull’s belief that it’s better to fix problems than prevent errors.&lt;/p&gt;&lt;p&gt;&lt;b&gt;23. The level of feedback you get is so much more valuable and impactful…. The problem with showing something to consumers when it’s almost totally done, people don’t necessarily want to give negative feedback at that point because it looks like, “This company has spent a lot of money already getting it to this stage and now I’m going to tell them, ‘It sucks.’” On the other hand, if something hangs together with tape, and it’s clear that it’s an early prototype, the mindset of consumers often is, “These people still need some help, so let me tell you what I really think about it.”&lt;/b&gt;&lt;/p&gt;&lt;p&gt;24. Analogous to Limb’s findings, Ansari and Berkowitz found that during improvisation, the right-temporoparietal junctions of the pianists’ brains were deactivated. Neuroscientists associate this area of the brain with the ability to make judgments, particularly about differences differences between self and others. The experienced pianists seemed to be able to turn off a judging part of their mind, freeing them up to create novel melodies. According to Berkowitz, brain scans of nonartists do not exhibit a similar pattern, which suggests that experiencing creative processes could help to build certain creative muscles.&lt;/p&gt;&lt;p&gt;25. There are several major improvisation principles. One is that you should “accept every offer.”&lt;/p&gt;&lt;p&gt;&lt;b&gt;26. So, for example, if two people were performing an improvised skit, Bob might say to Sherry, “I was thinking we could watch Silence of the Lambs tonight.” To which Sherry would accept that offer by saying something like, “Yes, and we can still have time to watch the Late Show afterward.” To which Bob might reply, “Yes, and then we can check email!” It’s a simplified and somewhat silly example, but the point of accepting every offer is that nothing is too silly.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;27. After all, if Sherry had spurned Bob’s initial offer by replying, “That’s a stupid movie,” Bob would start thinking and censoring himself, shutting down the possibilities that might come. The effect is deadening.&lt;/p&gt;&lt;p&gt;28. That Bob and Sherry lift one another up relates to another foundation of improvisation: Make your partner look good. Because Bob and Sherry aren’t criticizing one another, it creates a positive atmosphere to generate possibilities. Positive energy drives improvisation, and reduces inhibitions and doubts. By making each other look good, it’s easier for Bob and Sherry to get in a zone.&lt;/p&gt;&lt;p&gt;&lt;b&gt;29. Throughout the Pixar creative process, they rely heavily on what they call plussing; it is likely the most-used concept around the company. The point of plussing is to build upon and improve ideas without using judgmental language. Creating an atmosphere where ideas are constantly being plussed, while maintaining a sense of humor and playfulness, is a central element of Pixar’s magic.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;30. Rather than criticize an idea in its entirety (even if they don’t think it’s good), people accept the starting point before suggesting improvements.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;31. Instead of criticizing the sketch or saying “no,” the director will build on the starting point by saying something like, “I like Woody’s eyes, and what if we …” Again, notice the use of the word and rather than a word that implies a judgment, such as but.&lt;/p&gt;&lt;p&gt;32. A host of studies indicates that humor creates positive group effects. Many focus on how humor can increase cohesiveness and act as a lubricant to facilitate more efficient communications.&lt;/p&gt;&lt;p&gt;33. One reason for that is that humor has also been demonstrated to increase trust. In order to produce positive mental effects, however, researchers Eric Romero and Anthony Pescosolido found that humor first must be considered funny to the people involved, not seen as demeaning, derogatory, or put-downs.&lt;/p&gt;&lt;p&gt;34. Romero and Pescosolido argued, based on a broad assessment of humor research, should affirm group identities in terms of: who we are, what we are doing, and how we do things.&lt;/p&gt;&lt;p&gt;35. Research evidence suggests a strong link between inquisitiveness and creative productivity.&lt;/p&gt;&lt;p&gt;36. Innovators routinely networked with people who came from different backgrounds.&lt;/p&gt;&lt;p&gt;37. Learning a little bit from a lot of people was one of the main ways Tim identified so many unique ideas and insights.&lt;/p&gt;&lt;p&gt;38. Wiseman’s research is that lucky people pay more attention attention to what’s going on around them than unlucky people.&lt;/p&gt;&lt;p&gt;&lt;b&gt;39. For example, Wiseman found that the lucky people had three times greater open body language in social situations than unlucky people. Lucky people also smiled twice as much as unlucky people, thus drawing other people and chance encounters to them.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;40. von Hippel found that one group, which he called active users or lead users, were responsible for developing over 75 percent of the innovations.&lt;/p&gt;&lt;p&gt;41. These people not only serve as cutting-edge taste makers, they actively tinker to push and create new ideas on their own.&lt;/p&gt;&lt;p&gt;42. Since the needs of these active users precede and, according to the research, often anticipate, what the masses will like, they become incredibly valuable as idea development partners.&lt;/p&gt;&lt;p&gt;43. The ideas they help generate can then be tested with broader audiences and commercialized. The same is true for good comedy.&lt;/p&gt;&lt;p&gt;44. For example, singer and songwriter John Legend does so as he develops a new song. The first part of Legend’s songwriting process is to develop a good back-beat working closely with music producers like Kanye West, will.i.am, or Raphael Saadiq. Once he’s found a beat he likes, Legend will then develop melodies on top of it, often working at a piano, and finally write the lyrics. Kanye West, in particular, is a classic innovator and active user, constantly consuming and tinkering with music. Legend and others consider West to be a creative genius in many respects, with a well-tuned ear for what broader audiences will like. His early involvement in a song routinely drives later success. So, Legend will bounce ideas around with West at all stages of the process, especially at the beginning. It’s strategic trial and error.&lt;/p&gt;&lt;p&gt;45. A small win is “a concrete, complete, implemented outcome of moderate importance.” They are small successes that emerge out of our ongoing development process, and it’s important to be watching closely for them.&lt;/p&gt;&lt;p&gt;46. Small wins are like footholds or building blocks amid the inevitable uncertainty of moving forward.&lt;/p&gt;&lt;p&gt;47. They serve as what Saras Sarasvathy calls landmarks, and they can either confirm that we’re heading in the right direction or they can act as pivot points, telling us how to change course.&lt;/p&gt;&lt;p&gt;48. It is helpful for alcoholics to focus on remaining sober one day at a time, or even one hour at a time. Stringing together successive days of sobriety helps them to see the rewards of abstinence and makes it more achievable.&lt;/p&gt;&lt;p&gt;49. “Once a small win has been accomplished, forces are set in motion that favor another small win.”&lt;/p&gt;&lt;p&gt;50. Another benefit of small wins is less immediately obvious: They enable the development of the means to attain goals. Recall from Saras Saravathy’s research how important the development of means is to seasoned entrepreneurs. Entrepreneurs use their available means, such as their expertise, networks, or financial resources, to develop their ideas and access additional resources and means. One way that Ed Catmull (and later Steve Jobs) developed Pixar’s means was by steadily bringing in new talent with complementary skills.&lt;/p&gt;&lt;p&gt;51. Additional resources also flow toward winners.&lt;/p&gt;&lt;p&gt;52. One last, yet important, point about small wins is that often, rather than validating a direction we’ve been pursuing, they will provide a signal to proceed in a different way.&lt;/p&gt;&lt;p&gt;53. It is much easier to decide to make a change of approach when we are doing so not because things aren’t working but because something has started to work.&lt;/p&gt;&lt;/div&gt;</description><author>Chester Grant</author><pubDate>Sun, 31 Dec 2023 03:55:18 GMT</pubDate><guid isPermaLink="true">https://www.chestergrant.com/summary-little-bets-by-peter-sims</guid></item><item><title>Evolving web scraping: How Pageripper API handles JavaScript-heavy sites</title><link>https://zackproser.com/blog/evolving-web-scraping-pageripper-api</link><description>Pageripper API uses Puppeteer and headless Chrome under the hood to see the same thing your browser does, even for Single Page Applications (SPAs)</description><author>Zachary Proser</author><pubDate>Sun, 31 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/evolving-web-scraping-pageripper-api</guid></item><item><title>Better Corporate Communication</title><link>https://jonpauluritis.com/articles/corporate-communication-tips/</link><description>&lt;p&gt;I originally wrote this stuff up for my wife's company which was struggling with it but then ended up giving a talk about it. And since I basically post all of my important talks as articles here it is for your reading pleasure.&lt;/p&gt;
&lt;p&gt;TLDR; &lt;strong&gt;Communication can be made more or less effective by working with or against Human biological limitations.&lt;/strong&gt; To put it another way, the way you organize and provide your information can increase your throughput, retention, and utility of the information you are providing.&lt;/p&gt;
&lt;h3&gt;Human Biology&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;People can read and comprehend up to 400 WPM but average around 200 WPM&lt;/li&gt;
&lt;li&gt;People can listen and comprehend up to 150 WPM&lt;/li&gt;
&lt;li&gt;People can type ~45 WPM on average, with an upper limit towards 80 WPM for stronger typists.&lt;/li&gt;
&lt;li&gt;People will average roughly 100 - 150 WPM when presenting&lt;/li&gt;
&lt;li&gt;Human beings are capable of memorizing a shuffled deck of cards in ~19 seconds, but normal people can remember roughly 7 items for 20- 30 seconds (&lt;a href="https://doi.apa.org/fulltext/2017-22544-001.html"&gt;link&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Understand Timecosts&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Let's assume a hypothetical cost of $100/ hr per person after benefits etc (using round numbers for ease).&lt;/em&gt; This is roughly what costs would look like for 1 person communicating, and different size groups of being communicated to.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: center;"&gt;People&lt;/th&gt;
&lt;th style="text-align: center;"&gt;1hr Meeting&lt;/th&gt;
&lt;th style="text-align: center;"&gt;30min Meeting&lt;/th&gt;
&lt;th style="text-align: center;"&gt;Long Email&lt;/th&gt;
&lt;th style="text-align: center;"&gt;Short Email&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: center;"&gt;8&lt;/td&gt;
&lt;td style="text-align: center;"&gt;$800&lt;/td&gt;
&lt;td style="text-align: center;"&gt;$400&lt;/td&gt;
&lt;td style="text-align: center;"&gt;$275&lt;/td&gt;
&lt;td style="text-align: center;"&gt;&lt;strong&gt;~$110&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center;"&gt;4&lt;/td&gt;
&lt;td style="text-align: center;"&gt;$400&lt;/td&gt;
&lt;td style="text-align: center;"&gt;$200&lt;/td&gt;
&lt;td style="text-align: center;"&gt;$175&lt;/td&gt;
&lt;td style="text-align: center;"&gt;~$75&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center;"&gt;2&lt;/td&gt;
&lt;td style="text-align: center;"&gt;$200&lt;/td&gt;
&lt;td style="text-align: center;"&gt;&lt;strong&gt;$100&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: center;"&gt;$125&lt;/td&gt;
&lt;td style="text-align: center;"&gt;~$58&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Example: A long email: It takes 60 minutes to write and 15 min read. A short email: It takes roughly 15-30min to write and 5min read&lt;/p&gt;
&lt;p&gt;Let's compare some real-world scenarios. We're working on defining design/requirement tasks for an upcoming &amp;quot;widget&amp;quot;.&lt;/p&gt;
&lt;p&gt;An expensive way to do this might look like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;60min &amp;quot;brainstorming&amp;quot; with 8 people ($800-$1000).&lt;/li&gt;
&lt;li&gt;1 person writing down Tasks for 30min (~$50),&lt;/li&gt;
&lt;li&gt;A 30 min decision meeting with the group to close out ($400 - $500)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Total Cost: $1250 - $1500&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A cheaper way might look like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One person sits down alone and writes out 90% of the tasks in an email to 8 people ($275),&lt;/li&gt;
&lt;li&gt;Everyone reads the email ($100- $150)&lt;/li&gt;
&lt;li&gt;3 people send feedback and additions ($225)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Total Cost: $600 - $650&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Naively, the above examples make it seem like all communication should be written and/or meetings are expensive. This is not true. This example is just designed to show how real factors such as human biology can impact the throughput and efficiency of communication.&lt;/p&gt;
&lt;h1&gt;Tricks and Tips&lt;/h1&gt;
&lt;h3&gt;Choose the right medium for your message&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Email Messages&lt;/strong&gt; (or confluence type systems):
&lt;ul&gt;
&lt;li&gt;Anything you might need to reference later and/or search for&lt;/li&gt;
&lt;li&gt;Communication that might be shared&lt;/li&gt;
&lt;li&gt;Long communication where people might need time to process (reports)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Phone Calls&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Communication that might require more clarity or further explanation&lt;/li&gt;
&lt;li&gt;Multiple decisions reliant on each other (decisions in a chain)&lt;/li&gt;
&lt;li&gt;Communication that is emotional&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Async Communication&lt;/strong&gt; =&amp;gt; Text Message, Slack/Teams
&lt;ul&gt;
&lt;li&gt;Quick questions where you don't need a record&lt;/li&gt;
&lt;li&gt;Sharing links or other resources&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Text Message&lt;/strong&gt; =&amp;gt; for Urgency.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hand Written&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Great at expressing gratitude or respect&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here's an obtuse way to think about it:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: center;"&gt;Medium&lt;/th&gt;
&lt;th style="text-align: center;"&gt;Cost&lt;/th&gt;
&lt;th style="text-align: center;"&gt;Quick Feedback&lt;/th&gt;
&lt;th style="text-align: center;"&gt;Shareable&lt;/th&gt;
&lt;th style="text-align: center;"&gt;Searchable&lt;/th&gt;
&lt;th style="text-align: center;"&gt;Asynchronous&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: center;"&gt;Meeting&lt;/td&gt;
&lt;td style="text-align: center;"&gt;$$$$&lt;/td&gt;
&lt;td style="text-align: center;"&gt;✓&lt;/td&gt;
&lt;td style="text-align: center;"&gt;&lt;/td&gt;
&lt;td style="text-align: center;"&gt;&lt;/td&gt;
&lt;td style="text-align: center;"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center;"&gt;Email&lt;/td&gt;
&lt;td style="text-align: center;"&gt;$$&lt;/td&gt;
&lt;td style="text-align: center;"&gt;&lt;/td&gt;
&lt;td style="text-align: center;"&gt;✓&lt;/td&gt;
&lt;td style="text-align: center;"&gt;✓&lt;/td&gt;
&lt;td style="text-align: center;"&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center;"&gt;Phone&lt;/td&gt;
&lt;td style="text-align: center;"&gt;$$&lt;/td&gt;
&lt;td style="text-align: center;"&gt;✓&lt;/td&gt;
&lt;td style="text-align: center;"&gt;&lt;/td&gt;
&lt;td style="text-align: center;"&gt;&lt;/td&gt;
&lt;td style="text-align: center;"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center;"&gt;SMS/ Slack&lt;/td&gt;
&lt;td style="text-align: center;"&gt;$&lt;/td&gt;
&lt;td style="text-align: center;"&gt;✓&lt;/td&gt;
&lt;td style="text-align: center;"&gt;&lt;/td&gt;
&lt;td style="text-align: center;"&gt;✓&lt;/td&gt;
&lt;td style="text-align: center;"&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center;"&gt;Handwritten&lt;/td&gt;
&lt;td style="text-align: center;"&gt;$$$$&lt;/td&gt;
&lt;td style="text-align: center;"&gt;&lt;/td&gt;
&lt;td style="text-align: center;"&gt;✓&lt;/td&gt;
&lt;td style="text-align: center;"&gt;&lt;/td&gt;
&lt;td style="text-align: center;"&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center;"&gt;Carrier Pidgeon&lt;/td&gt;
&lt;td style="text-align: center;"&gt;$$$$$&lt;/td&gt;
&lt;td style="text-align: center;"&gt;&lt;/td&gt;
&lt;td style="text-align: center;"&gt;&lt;/td&gt;
&lt;td style="text-align: center;"&gt;&lt;/td&gt;
&lt;td style="text-align: center;"&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;How to handle meetings&lt;/h3&gt;
&lt;p&gt;&lt;u&gt;Per Andy Grove&lt;/u&gt;: There are 2 types of meetings: &lt;strong&gt;Process-oriented meetings&lt;/strong&gt; and &lt;strong&gt;Mission-oriented Meetings&lt;/strong&gt;. Generally, they boil down to &lt;strong&gt;Information sharing&lt;/strong&gt; or &lt;strong&gt;decision-making&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Process-oriented meetings:&lt;/strong&gt; are to identify latent problems, course-correct issues, create alignment, make type 2 decisions, and share information (&lt;em&gt;think: standup, one-on-ones, etc&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mission-oriented meetings:&lt;/strong&gt; are to make (type-1) decisions (&lt;em&gt;should be rare&lt;/em&gt;) [strategic/ irreversible]&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are a lot of other authors or business leaders to talk about this as well.&lt;/p&gt;
&lt;p&gt;&lt;u&gt;&lt;strong&gt;Meeting Tips and Tricks&lt;/strong&gt;&lt;/u&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Written Communication &amp;gt; Meetings&lt;/li&gt;
&lt;li&gt;Meetings are the best mediums for:
&lt;ul&gt;
&lt;li&gt;A series of dependent quick decisions that impact a lot of people.&lt;/li&gt;
&lt;li&gt;Information share, especially where deeper questions may arise&lt;/li&gt;
&lt;li&gt;Creating alignment on key decisions&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Meetings are bad for:
&lt;ul&gt;
&lt;li&gt;Adhoc Brainstorming (which is different from workshopping)&lt;/li&gt;
&lt;li&gt;Type 2 decisions - decisions that are easily reversed.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Always invite the fewest possible number of people you can get away with- ESPECIALLY managers.
&lt;ul&gt;
&lt;li&gt;If you have to ask if a manager should be there... they probably don't need to be. Send them the notes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Try to book meetings for 20min or 40min. NOT 30min or 60min.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Power Point/ Slide Decks are used to present visual aids&lt;/strong&gt;*.
&lt;ul&gt;
&lt;li&gt;I wrote a special section about this below...&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Meeting Etiquette:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Show up prepared.
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;(Protip: If you are leading and you don't need to prepare... it probably shouldn't be a meeting)&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Send out an agenda ahead of time. Include goals and reference materials&lt;/li&gt;
&lt;li&gt;Send out action items&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;IMPORTANT! Slide Decks&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Misusing the slide deck format is HANDS DOWN the biggest mistake people make in corporate communication,&lt;/strong&gt; and it's not even close. Because it's so bad it's worth going into a little detail about.&lt;/p&gt;
&lt;p&gt;&lt;u&gt;Let's run through slide deck norms:&lt;/u&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An information/decision meeting is called so the person calling the meeting feels obligated to make a slide deck to make the meeting &amp;quot;professional&amp;quot;, organized, or to have an artifact for later.&lt;/li&gt;
&lt;li&gt;The person quickly throws up their &lt;strong&gt;jumble of text notes onto a slide deck in itty-bitty size 10 font&lt;/strong&gt; just before the meeting. Because it is a giant rat's nest of text they spread it over 10-15 slides.&lt;/li&gt;
&lt;li&gt;When they show up the &lt;strong&gt;speaker reads straight from their deck&lt;/strong&gt; because they didn't rehearse.&lt;/li&gt;
&lt;li&gt;Because people read faster than they can hear they either finish reading before the speaker finishes talking OR they struggle to read the small text and ignore the speaker&lt;/li&gt;
&lt;li&gt;They format the deck as a &amp;quot;narrative&amp;quot; which positions a climax (decision) later... In this format, &lt;strong&gt;executives and managers will constantly interrupt&lt;/strong&gt; to point out the thing that is on the next slide.&lt;/li&gt;
&lt;li&gt;With all of these distractions, the decision gets lost and the meeting goes long. Another meeting with the same people will often be called or the decision/information will become muddled.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Notes on how to use slide decks properly&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The slide deck medium is a &lt;strong&gt;&amp;quot;speech with visual aides.&amp;quot;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Don't feel you need to use a deck. Many meetings could benefit by using a one-pager instead.&lt;/li&gt;
&lt;li&gt;You should need to rehearse if it is a &amp;quot;speech with visual aides&amp;quot;. Make sure you rehearse.&lt;/li&gt;
&lt;li&gt;If your slide deck is primarily text don't use a deck. Formatting wastes time and &lt;strong&gt;People struggle to Read and Listen at the same time&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;u&gt;Narratives (story with climax) SHOULD NOT be presented as a slide deck.*&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;&lt;u&gt;Reports (numerical or empirical information) SHOULD NOT be presented as a slide deck.&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;Visual aids (e.g. graphs, pictures) are a superpower. They can convey more information in a smaller time and less space. Use these media extensively.&lt;/li&gt;
&lt;li&gt;After every part of your speech/ each slide ask yourself: &amp;quot;So what?&amp;quot; or &amp;quot;Why is this important&amp;quot;... if you are unable to answer that question fix the section/slide.&lt;/li&gt;
&lt;li&gt;Executives love to interrupt slide decks. It should be a goal of yours to avoid interruptions.
&lt;ul&gt;
&lt;li&gt;They will not interrupt if all of the most important stuff is at the front of the meeting stated concisely.&lt;/li&gt;
&lt;li&gt;They will not interrupt if &amp;quot;So what&amp;quot; or &amp;quot;why&amp;quot; is answered.&lt;/li&gt;
&lt;li&gt;They will cut the meeting short if there is enough information to make a decision (GREAT)&lt;/li&gt;
&lt;li&gt;They will cut a meeting short if more information gathering is required (GOOD)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;* I talk about Form/ Formatting in the next section&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Form/Formatting your Written Communication&lt;/h3&gt;
&lt;p&gt;The form/ format you present information can increase efficiency, retention, and the utility of the communication. The following picture is a minimalist way of viewing different formats:&lt;/p&gt;
&lt;p&gt;[insert picture here]&lt;/p&gt;
&lt;p&gt;Here are some notes about different formats for your written communication. Keep in mind that a corporate setting (internal) is very different than a corporate setting (external), or things like art. A well-built narrative format is great for Fiction, but TERRIBLE for a corporate email.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AVOID Narrative Format (generally)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Background, Build Conflict, Climax, and Resolution&lt;/li&gt;
&lt;li&gt;&lt;em&gt;(Note: There are many exceptions such as mission-related communication, product releases, etc)&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Journalism Format/ BLUT&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Format: Top to bottom, Most important info to least important info&lt;/li&gt;
&lt;li&gt;Great for emails, reports, some presentations&lt;/li&gt;
&lt;li&gt;The format was designed so that editors can compose a publication quickly. That is, they know that they can chop off bottom up based on how long they want a piece to be.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Academic Format / Experiments&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Format: Abstract or Summary of Results (most important info) then sections with background research, ideation, hypothesis, methods, and citations&lt;/li&gt;
&lt;li&gt;Great for reports, presentations, etc&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Outline Format/ Information systems&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Format: Information in a hierarchy, with the most important at the top and top level, moving down toward elements of lesser importance.&lt;/li&gt;
&lt;li&gt;&amp;quot;The Classic model&amp;quot;
&lt;ul&gt;
&lt;li&gt;Wisdom (top level),&lt;/li&gt;
&lt;li&gt;Information (second level),&lt;/li&gt;
&lt;li&gt;Data (bottom level)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;How to improve your Content&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;So What??? Why should I care Theory&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Make sure your communication answers the question &lt;strong&gt;&amp;quot;So What?&amp;quot;&lt;/strong&gt; or &lt;strong&gt;&amp;quot;Why should someone care?&amp;quot;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use Examples.&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Human beings are hardwired for examples. Show &amp;gt; Tell&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Provide Context&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;If we're working as a team effectively &lt;strong&gt;we're usually working on very different things.&lt;/strong&gt; That's a good thing... the only caveat is that you will probably need to provide context on the thing you are talking about when we get together.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simplify- Less is more&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;People can really only remember 3-5 things from a 30-60 meeting. Don't try to do more than that in a meeting.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Writing Tips&lt;/h2&gt;
&lt;h3&gt;Use Active Writing if longer than 1-2 Sentences&lt;/h3&gt;
&lt;p&gt;EXAMPLE&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Break things into &lt;strong&gt;SECTIONS&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use bold, Underlining, CAPS, and italics&lt;/strong&gt; to emphasize key points&lt;/li&gt;
&lt;li&gt;Change Font Sizes to change flow&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use Bullets&lt;/strong&gt; but no more than 3-5 bullets&lt;/li&gt;
&lt;li&gt;Delete anything in parenthesis, unless it's a useful reference&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Words&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;DON'T USE Acronyms unless your Grandmother would know it. Companies LOVE acronyms... but they slow down communication.&lt;/li&gt;
&lt;li&gt;Replace Adjectives with Data
&lt;ul&gt;
&lt;li&gt;&amp;quot;Nearly all users&amp;quot; =&amp;gt; &amp;quot;93% of users&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;We did significantly better&amp;quot; =&amp;gt; &amp;quot;We increased our performance 25 points&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Only use $10 Words when there are no better options.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Know your Audience&lt;/h2&gt;
&lt;p&gt;I'm going to write another article about this at some other time but it's important to know who your audience is. One of the first ways to think about it is at an organizational level. Though there are other forms of corporate structure the most common in the United States, Japan, and the Western world is a hierarchal corporate structure. The Japanese (naturally) have a thing for this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vertical communication | Reporting  (&amp;quot;Hokoku&amp;quot;)&lt;/li&gt;
&lt;li&gt;Horizontal communication | Informing (&amp;quot;Renraku&amp;quot;)&lt;/li&gt;
&lt;li&gt;Both directions | Consulting  (&amp;quot;Sodan&amp;quot;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Cultural, ESL, and Subculture factors&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Credit to Charlie Main for teaching me this. A weird cultural thing that the United States does is that we use a lot of sports analogies in business to communicate. Turns out that the rest of the world doesn't do this. Avoid sports analogies in business comms when you work in a multicultural/ multi-ethnic environment. When you are working with Americans... increase your use.&lt;/li&gt;
&lt;li&gt;People who speak English as a second language are going to prefer written communication.&lt;/li&gt;
&lt;li&gt;Certain personalities gravitate towards types of communication. Leverage this fact. Software Engineers READ and WRITE text all day long, therefore use written communication. Artists view graphics all day long, therefore use visual communication. Sales people and managers talk all day long... use verbal communication.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Lastly, Conclusions&lt;/h3&gt;
&lt;p&gt;Conclusions are one of the most important parts to communication. It will be the thing that people remember the most from your communication. So don't just end abruptly.&lt;/p&gt;
&lt;p&gt;Thank you.&lt;/p&gt;</description><author>JonPaulUritis.com</author><pubDate>Sun, 31 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jonpauluritis.com/articles/corporate-communication-tips/</guid></item><item><title>Year in review</title><link>https://blog.imraniqbal.org/2023-review/</link><description>&lt;p&gt;I thought I should do a recap for this year.
Though also for some typing practice.&lt;/p&gt;
&lt;p&gt;I started this year unemployed, and still burned out from the last job.
I do not remember the states of my finances, but I do get a new job later on&lt;/p&gt;
&lt;p&gt;In no particular order:&lt;/p&gt;
&lt;h3 id="momo"&gt;&lt;a class="anchor" href="https://blog.imraniqbal.org/2023-review/#momo"&gt;
    #
&lt;/a&gt;
Momo&lt;/h3&gt;
&lt;p&gt;I got my cat Momo.
She is on the opposite end of the personality spectrum compared to Yar.
She is confident and unfazed by new people.
She even purrs at the vet.&lt;/p&gt;
&lt;p&gt;She is also overweight and wants to eat everything.
I had to buy some fancy food bowls that specifically open for registered chips.
It has been an ordeal trying to keep the cats from each others food.
Momo is not allowed to eat Yar's food as she is fat and needs to lose weight.
Yar is not allowed to eat Momo's food as he has allergies and requires medication.&lt;/p&gt;
&lt;p&gt;She loves to play with toys.
Though so did Yar when he was her age (2).
He no longer cares much about them at 4.
We will see if she follows a similar route.&lt;/p&gt;
&lt;h3 id="hogwarts-legacy"&gt;&lt;a class="anchor" href="https://blog.imraniqbal.org/2023-review/#hogwarts-legacy"&gt;
    #
&lt;/a&gt;
Hogwarts Legacy&lt;/h3&gt;
&lt;p&gt;This game sucked ass.
Complete waste of $102 CAD.
I wish I never bought and played it.
It shouldn't even be in this post but I want to vent about just how much of a let down that game is.
Incredible IP wasted on a subpar game.&lt;/p&gt;
&lt;h3 id="my-partner"&gt;&lt;a class="anchor" href="https://blog.imraniqbal.org/2023-review/#my-partner"&gt;
    #
&lt;/a&gt;
My Partner&lt;/h3&gt;
&lt;p&gt;She left Canada to pursue her PhD in the states.
I am hoping to move there too to support her.
She has been having a rough time, but I think she will pull through.&lt;/p&gt;
&lt;h3 id="side-stuff"&gt;&lt;a class="anchor" href="https://blog.imraniqbal.org/2023-review/#side-stuff"&gt;
    #
&lt;/a&gt;
Side stuff&lt;/h3&gt;
&lt;p&gt;Activity on my usent downloader has stalled since starting a new job.
It's hard to find time in the day to both gym and have time left over for programming after work.&lt;/p&gt;
&lt;p&gt;I have managed to rework my dad's site stuff for a third time, this time with NixOS.
There should be an upcoming blog post about it.&lt;/p&gt;
&lt;p&gt;The hard part, as mentioned before, is finding the time now that I employed again.&lt;/p&gt;
&lt;h3 id="my-old-team"&gt;&lt;a class="anchor" href="https://blog.imraniqbal.org/2023-review/#my-old-team"&gt;
    #
&lt;/a&gt;
My old team&lt;/h3&gt;
&lt;p&gt;I got to meet my old team in person.
I quit right before there the company get together post COVID.
They are a nice and fun bunch but "stuck" at a crap company that pays too much 😉.&lt;/p&gt;
&lt;h3 id="my-new-team"&gt;&lt;a class="anchor" href="https://blog.imraniqbal.org/2023-review/#my-new-team"&gt;
    #
&lt;/a&gt;
My new team&lt;/h3&gt;
&lt;p&gt;I started a new job and met my new team in person too!
The name of the place is Psiphon and they work in internet cencorship circumnavigation.
Smart folks that work primarily in go.
If there is one thing I am grateful for it's that I wont have to touch ruby again for a long time.&lt;/p&gt;
&lt;p&gt;Work culture at the company is unique, hands off and low meetings.
The company places a lot of trust in the employees to get the job done and I hope I don't betray that trust.
Not fired yet.&lt;/p&gt;</description><author>Imran's Blog</author><pubDate>Sun, 31 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.imraniqbal.org/2023-review/</guid></item><item><title>Reflection on 2023</title><link>https://hth.is/2023/12/31/reflection-on-2023/</link><description>Another year, another reflection.
Theme Link to heading Ever since seeing CGP Grey&amp;rsquo;s video on yearly themes back in 2020 I&amp;rsquo;ve stuck to the practice, and it has worked pretty well as a mild forcing function.
In 2022 it was &amp;lsquo;Health&amp;rsquo; and this year&amp;rsquo;s it was &amp;lsquo;Growth &amp;amp; Learning&amp;rsquo;. Where possible I chose opportunities or activities that would allow for one or both.
It would be impossible to list everything I&amp;rsquo;ve picked up through the various mediums available, and so I won&amp;rsquo;t even try.</description><author>Hrafn Thorvaldsson</author><pubDate>Sun, 31 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://hth.is/2023/12/31/reflection-on-2023/</guid></item><item><title>2023 Review</title><link>https://www.danielcorin.com/posts/2023/2023-review/</link><description>End of year review</description><author>Thought Eddies</author><pubDate>Sun, 31 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/posts/2023/2023-review/</guid></item><item><title>2023 Year in Review</title><link>https://benovermyer.com/blog/2023/12/2023-year-in-review/</link><description>&lt;p&gt;2023 was a year of important changes and improvements in my life.
My goals for the year according to last year's &lt;a href="https://benovermyer.com/blog/2022/12/2022-year-in-review/"&gt;"Looking Ahead to 2023"&lt;/a&gt; post
ended up going wide of the mark. I didn't succeed at the SGG goals
and I abandoned #dungeon23 early in January. I also didn't keep to
my ban on new entertainment. Where I did succeed, though, was in
the fitness goals for the year. More on that in a moment.&lt;/p&gt;
&lt;p&gt;This year, I'll start including a few notes about what I accomplish
at my day job.&lt;/p&gt;
&lt;h2 id="apiture"&gt;Apiture&lt;/h2&gt;
&lt;p&gt;At my day job this year the biggest achievement was converting our
monitoring stack from New Relic to Prometheus+Grafana. The one gap
that remains is the profiling (what New Relic calls "APM"). I also
continued to make small improvements in several areas, like the new
ECS-based Wordpress architecture.&lt;/p&gt;
&lt;h2 id="iron-arachne"&gt;Iron Arachne&lt;/h2&gt;
&lt;p&gt;Iron Arachne had a big year this year. In March, it reached its fifth
anniversary. Over the course of the year I made many updates to the
code to clean it up and reorganize it. The most important updates,
though, were the SvelteKit migration and the rework of the planet generator.&lt;/p&gt;
&lt;p&gt;Prior to the SvelteKit migration, Iron Arachne was running as a static site
on Netlify. However, when I migrated it, it became a Node.js application
running on my own web server. This made it more difficult to manage, but
gave me way more freedom in the long run.&lt;/p&gt;
&lt;p&gt;The planet generator rework happened in the last quarter of the year. Previously,
the images were set up as 3D scenes in THREE.js, with a sphere for the planet,
another sphere for clouds if they were present, a plane for the starfield, and
camera and light rigging. The rework turned them into a single shader capturing
everything. I finally implemented atmospheric scattering and dramatically improved
how the various planets looked. I also used this new rendering approach for the
star nation and star system generators.&lt;/p&gt;
&lt;h2 id="self-hosting"&gt;Self Hosting&lt;/h2&gt;
&lt;p&gt;Besides Iron Arachne, I also began to self host more things. I bought a mini PC,
installed Ubuntu on it, and set up several services on it. It's accessible via
our Tailscale network, meaning I can access it from any device that is part of
that network. Right now I have the following hosted on it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Jellyfin: for media&lt;/li&gt;
&lt;li&gt;Kavita: for browsing and reading PDFs&lt;/li&gt;
&lt;li&gt;IT Tools: for lots of little convenience utilities&lt;/li&gt;
&lt;li&gt;Paperless: for scanning and storing important documents&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While Paperless is set up, I didn't really start using it properly in 2023. Same
for Jellyfin. I've used Kavita and IT Tools more.&lt;/p&gt;
&lt;h2 id="my-old-sites"&gt;My Old Sites&lt;/h2&gt;
&lt;p&gt;I recreated as many of my old websites as I could find archived online and put them
online again on a subdomain of my main site. Some of these I'd completely forgotten
existed. In the course of this work, I also took inspiration from my old art galleries
to rework the art section of my main site. It reminded me of how much I used to love
making and sharing art.&lt;/p&gt;
&lt;h2 id="reading-books"&gt;Reading Books&lt;/h2&gt;
&lt;p&gt;Holy crap. I read 34 books in 2023, which is three times what I read in 2022, and twice
my previous record. Granted, a lot of it was light fiction, but I still spent way
more time reading this year.&lt;/p&gt;
&lt;p&gt;One reason for this is that I decided to create a new fan site for one of the fictional
cultures in the Valdemar novels by Mercedes Lackey. Before I can do that, I wanted to
refamiliarize myself with them, and so I began the process of reading every single novel
that they appear in. I forgot how much I enjoy these books. It's been a delight reading
them. Some of them are completely new to me, too, which is awesome.&lt;/p&gt;
&lt;p&gt;I also finished the Honor Harrington series of novels by David Weber. Those were a harder
read, full of exposition and dense text. I enjoyed them too though.&lt;/p&gt;
&lt;h2 id="health-and-fitness"&gt;Health and Fitness&lt;/h2&gt;
&lt;p&gt;I started 2023 with a ban on alcohol that ended somewhere near the end of January. Running
became a regular routine for me; I ran in the morning on Mondays, Wednesdays, and Fridays
before work. My cardio health improved a lot; my VO2 max went from a low of 33.9 in January
to a high of 37.4 in April and a more sustained peak of 36.8 in the latter half of the year.
My cardio recovery went from 19 BPM in January to 37 BPM by the end of the year.&lt;/p&gt;
&lt;p&gt;I stopped running in November when I undertook a self-imposed wellness challenge. For the
month of November, I drank no alcohol and began a bodybuilding routine at the gym. Four
days of the week I would go to the gym and lift weights. Cardio was limited to only a few
minutes of cooldown walking at the end of each workout.&lt;/p&gt;
&lt;p&gt;The amount of weight I can lift improved a bit, but what really improved was my confidence
and interest in bodybuilding. Now I just need to reincorporate my cardio exercises and I'll
have a more complete fitness regimen.&lt;/p&gt;
&lt;h2 id="silver-gryphon-games"&gt;Silver Gryphon Games&lt;/h2&gt;
&lt;p&gt;We didn't make nearly as much progress on SGG in 2023 as I expected. I had planned on releasing
at least one new book by the end of March. We only released two playtest books later in the
summer; the new Aether and Ingenium editions. Despite trying to keep on top of Aethermancy it
ended up being a struggle to get the manuscript finished. As of this writing, it's still not
done.&lt;/p&gt;
&lt;p&gt;Aether and Ingenium's second editions got completed manuscripts. Aether got a lot of editing.
The rise of AI art has made it a lot more difficult to find cheap artwork made by real artists.&lt;/p&gt;
&lt;h2 id="previous-years"&gt;Previous Years&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2022/12/2022-year-in-review/"&gt;2022&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2021/12/2021-year-in-review/"&gt;2021&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2020/12/2020-year-in-review/"&gt;2020&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2018/12/2018-year-in-review/"&gt;2018&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2017/01/looking-back-on-2016-and-forward-to-2017/"&gt;2016&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2016/01/2015-year-in-review/"&gt;2015&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2013/12/2013-year-in-review/"&gt;2013&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Ben Overmyer's Site</author><pubDate>Sun, 31 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2023/12/2023-year-in-review/</guid></item><item><title>2023 in review</title><link>https://cheeaun.com/blog/2023/12/2023-in-review/</link><description>This is probably going to be short as this year is perhaps the most unproductive year I've ever had.As usual, I start this off with a compilation…</description><author>cheeaunblog</author><pubDate>Sun, 31 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://cheeaun.com/blog/2023/12/2023-in-review/</guid></item><item><title>2023-12-31</title><link>https://ho.dges.online/pictures/2023-12-31/</link><description/><author>ho.dges.online</author><pubDate>Sun, 31 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ho.dges.online/pictures/2023-12-31/</guid></item><item><title>Emoji by Category</title><link>https://taylor.town/emoji-by-category</link><description>📝</description><author>taylor.town</author><pubDate>Sun, 31 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/emoji-by-category</guid></item><item><title>Emoji by Year</title><link>https://taylor.town/emoji-by-year</link><description>📝</description><author>taylor.town</author><pubDate>Sun, 31 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/emoji-by-year</guid></item><item><title>2024 Will Be the Year of the Blog</title><link>https://www.da.vidbuchanan.co.uk/blog/year-of-the-blog.html</link><author>David Buchanan's Blog</author><pubDate>Sun, 31 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.da.vidbuchanan.co.uk/blog/year-of-the-blog.html</guid></item><item><title>Gift Giving</title><link>https://foreverliketh.is/blog/gift-giving/</link><description>&lt;details&gt;
Image Details &amp; Source
&lt;p&gt;&lt;a href="https://unsplash.com/photos/person-showing-brown-gift-box-IPx7J1n_xUc"&gt;person showing brown gift box&lt;/a&gt; by &lt;a href="https://unsplash.com/@kadh"&gt;Kira auf der Heide&lt;/a&gt; on &lt;a href="https://unsplash.com/"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://foreverliketh.is/blog/gift-giving/gift-giver.jpg"&gt;Click Here for the Original, High-Quality Image&lt;/a&gt;&lt;/p&gt;
&lt;/details&gt;
&lt;hr /&gt;
&lt;p&gt;This page was last modified: &lt;strong&gt;December 31st, 2023&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This post is part of &lt;a href="https://indieweb.org/indieweb-carnival"&gt;IndieWeb Carnival&lt;/a&gt;. A project helping motivate people to write more on their personal websites. This month&amp;rsquo;s host is &lt;a href="https://dead.garden/"&gt;Johanna&lt;/a&gt; and their topic was &amp;lsquo;Holiday and December traditions&amp;rsquo; [&lt;a href="https://dead.garden/blog/indieweb-carnival-december-2023----holiday-and-december-traditions.html"&gt;#1&lt;/a&gt; &amp;amp; &lt;a href="https://dead.garden/blog/december-23-indieweb-carnival-roundup.html"&gt;#2&lt;/a&gt;]&lt;/p&gt;
&lt;p&gt;This post is also part of &lt;a href="https://forum.agoraroad.com/index.php?threads/agora-road-travelogue-december.6246/"&gt;Agora Road Travelogue - Dec&lt;/a&gt;. A project attempting to encourage individuals to both write and exercise ownership over their content.&lt;/p&gt;
&lt;hr /&gt;
&lt;h5 id="bah-humbug"&gt;Bah! Humbug! &lt;a class="anchor" href="#bah-humbug"&gt;#&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;Of all the holiday traditions I&amp;rsquo;m familiar with, the one I&amp;rsquo;ve caught myself engaging with the most year over year would have to be gift-giving. While I won&amp;rsquo;t extrapolate my experience with it to you, traveler, I&amp;rsquo;ve personally found the whole thing to be&amp;hellip; a bit of a process. And I&amp;rsquo;m not alone: A &lt;a href="https://sizeof.cat/post/lainring/"&gt;Lainchan Webring&lt;/a&gt; neighbor, &lt;a href="https://maerk.xyz/"&gt;Mark (maerk.xyz)&lt;/a&gt;, recently wrote about &lt;a href="https://maerk.xyz/blog/christmas/"&gt;their Christmas gifting struggles&lt;/a&gt;, expressing sentiments I totally relate to.&lt;/p&gt;
&lt;p&gt;In my opinion, part of the &lt;a href="https://en.wiktionary.org/wiki/grinchy"&gt;grinchy&lt;/a&gt; frustration that can stem from the practice is that its nature is more complicated than it appears at first glance. But I believe that, with a good mindset, a little preparation and maybe a couple handy strategies, we can keep ourselves from questioning why we even bother in the first place.&lt;/p&gt;
&lt;h5 id="ant-not-grasshopper"&gt;Ant, Not Grasshopper &lt;a class="anchor" href="#ant-not-grasshopper"&gt;#&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;&lt;a href="https://explodingtopics.com/blog/christmas-spending-stats"&gt;The average American currently spends approximately $648 USD on Christmas gifts&lt;/a&gt;; a little over &lt;a href="https://finance.yahoo.com/news/average-salary-state-us-152311356.html"&gt;1% of their yearly salary&lt;/a&gt;. A higher-end alternative is to cap the spending at 1 week&amp;rsquo;s salary. Play with that dial as you see fit, but I would try to stay in that range. Ideal gift-giving, imo, embodies the &amp;lsquo;&lt;a href="https://en.wikipedia.org/wiki/Goldilocks_principle"&gt;Goldilocks principle&lt;/a&gt;&amp;rsquo;, and budget is the first place it should be applied. Whatever the amount, &lt;a href="https://en.wiktionary.org/wiki/set_in_stone"&gt;set it in stone&lt;/a&gt; for the year and don&amp;rsquo;t look back.&lt;/p&gt;
&lt;p&gt;Alright, Santa Claus, now it&amp;rsquo;s time to make your list. Here&amp;rsquo;s the rough hierarchy: Family &amp;ndash;&amp;gt; Friends &amp;ndash;&amp;gt; Coworkers. Seems too obvious? You&amp;rsquo;d be surprised:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.psychologytoday.com/intl/blog/happiness-in-world/201407/why-people-can-be-kinder-strangers-loved-ones"&gt;Why People Can Be Kinder to Strangers Than to Loved Ones&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://theinspirationallifestyle.com/short-why-nice-guys-treat-their-loved-ones-worse-than-strangers/"&gt;Why &amp;ldquo;People Pleasers&amp;rdquo; Treat Their Loved Ones Worse Than Strangers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Additionally, the more distant the person, the easier it is to get them a &amp;ldquo;good&amp;rdquo; gift. Naturally, their absence of expectation produces a &amp;ldquo;low effort, high reward&amp;rdquo; effect; be wary of that trap. I&amp;rsquo;m not gonna throw out percentages, but if the bulk of your pot isn&amp;rsquo;t going to those that love you, there&amp;rsquo;s probably something worth reevaluating.&lt;/p&gt;
&lt;h5 id="modern-magi"&gt;Modern Magi &lt;a class="anchor" href="#modern-magi"&gt;#&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;Now for the &lt;em&gt;&lt;strong&gt;REAL&lt;/strong&gt;&lt;/em&gt; challenge: the gifts themselves. Balance can be key.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Not too cheap, but also not too expensive.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Not too giver-centered, but also not too recipient-centered.&lt;/strong&gt; It should have a piece of you, a personal touch, but just a bit.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Not too plain / boring, but also not 100% reaction bait.&lt;/strong&gt; Try to spice it up if it&amp;rsquo;s something bland, and don&amp;rsquo;t forget to consider the practicality (and size) if it&amp;rsquo;s something flashy.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Additional tips include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&lt;strong&gt;SOME&lt;/strong&gt;&lt;/em&gt; element of surprise; even if it&amp;rsquo;s just the timing. &lt;a href="https://youtu.be/WqYIkm66RSI"&gt;#1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Remember you can gift experiences (something to do), not just stuff. &lt;a href="https://youtu.be/xE9mPmWEOQ8"&gt;#2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Taking notes (being attentive) all year round, can save you hunting time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Need inspiration? I recommend:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.etsy.com/"&gt;Etsy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://old.reddit.com/r/GiftIdeas/"&gt;r/GiftIdeas&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/"&gt;YouTube&lt;/a&gt; Search: &amp;ldquo;Gift Ideas&amp;rdquo; + &amp;ldquo;Hobby / Gender / Interest / etc.&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id="forest-for-the-trees"&gt;Forest for the Trees &lt;a class="anchor" href="#forest-for-the-trees"&gt;#&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Gift"&gt;A gift or a present is an item given to someone &lt;em&gt;&lt;strong&gt;without the expectation of payment or anything in return.&lt;/strong&gt;&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;No matter the effort, there is &lt;em&gt;&lt;strong&gt;NO guarantee&lt;/strong&gt;&lt;/em&gt; that a present will be appreciated. The attempt of sincere giving alone should serve as both sufficient motivation to try, and its own reward. Sound a tad &lt;a href="https://en.wikipedia.org/wiki/Stoicism"&gt;stoic&lt;/a&gt;? Perhaps. But common alternative attitudes can prove problematic:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.ninertimes.com/opinion/opinion-christmas-has-become-a-capitalist-holiday/article_b604ced4-75a1-11ed-8f48-03541976df00.html"&gt;&amp;ldquo;Christmas has become a capitalist holiday&amp;rdquo;&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;You&amp;rsquo;re just a sucker falling for a scam, and the only way to win is not to play.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.theatlantic.com/culture/archive/2022/12/gift-giving-decision-making-psychology/672319/"&gt;&amp;ldquo;Gift Giving Is About the Buyer, Not the Receiver&amp;rdquo;&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;You&amp;rsquo;re actually just selfish, and only want to feel good about yourself.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pbs.org/newshour/economy/the-economics-of-wasteful-spending-the-dead-weight-loss-of-christmas"&gt;&amp;ldquo;The economics of wasteful spending: The deadweight loss of Christmas&amp;rdquo;&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;You&amp;rsquo;re, &lt;em&gt;&lt;strong&gt;literally&lt;/strong&gt;&lt;/em&gt;, burning money away when you engage in this.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These types of logical / cynical lenses that we can look at the tradition through, I think they do more harm than good. Oft-repeated advice in these circles include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Just give money.&lt;/li&gt;
&lt;li&gt;Ask the recipient what they want.&lt;/li&gt;
&lt;li&gt;Gift only &lt;a href="https://en.wiktionary.org/wiki/utilitarian"&gt;utilitarian&lt;/a&gt; items.&lt;/li&gt;
&lt;li&gt;Forgo gift-giving entirely.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&amp;rsquo;s like there&amp;rsquo;s an obsession with optimization, or a fatalist pessimism, or a fear of failure, or some notion of intellectual superiority. Citing research or studies, and declaring the findings as a sort-of gospel is par for the course. Fren, they have not &amp;ldquo;solved&amp;rdquo; gift-giving; they&amp;rsquo;ve developed a formula for mediocrity! And worse, it&amp;rsquo;s possibly a poison to the spirit of the act; a dehumanizing, soul-eating &lt;a href="https://en.wikipedia.org/wiki/Ebenezer_Scrooge"&gt;scrooge&lt;/a&gt;-toxin!&lt;/p&gt;
&lt;p&gt;Is the tradition such a burden? A custom out of time? A materialistic inconvenience? I don&amp;rsquo;t know, visitor. But I&amp;rsquo;ll be here, trying to get better at it anyways. If you have any, I&amp;rsquo;d love to hear your thoughts on what makes for good gift-giving.&lt;/p&gt;
&lt;p&gt;Happy New Year! 🎆&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Contributors: &lt;a href="https://en.wikipedia.org/wiki/Charles_Dickens"&gt;Charles Dickens&lt;/a&gt; &amp;ndash;&amp;gt; &lt;a href="https://en.wikipedia.org/wiki/A_Christmas_Carol"&gt;A Christmas Carol&lt;/a&gt; &amp;ndash;&amp;gt; &lt;a href="https://en.wikipedia.org/wiki/Ebenezer_Scrooge"&gt;Ebenezer Scrooge&lt;/a&gt; | &lt;a href="https://en.wikipedia.org/wiki/Serial_Experiments_Lain"&gt;Serial Experiments Lain&lt;/a&gt; &amp;ndash;&amp;gt; &lt;a href="https://lainchan.org/"&gt;Lainchan&lt;/a&gt; &amp;ndash;&amp;gt; &lt;a href="https://yukinu.com/"&gt;Yukinu&lt;/a&gt; &amp;ndash;&amp;gt; &lt;a href="https://sizeof.cat/"&gt;sizeof.cat&lt;/a&gt; | &lt;a href="https://en.wikipedia.org/wiki/Dr._Seuss"&gt;Dr. Seuss&lt;/a&gt; &amp;ndash;&amp;gt; &lt;a href="https://en.wikipedia.org/wiki/How_the_Grinch_Stole_Christmas!"&gt;How the Grinch Stole Christmas!&lt;/a&gt; | &lt;a href="https://explodingtopics.com/"&gt;Exploding Topics&lt;/a&gt; &amp;ndash;&amp;gt; &lt;a href="https://howjosh.com/"&gt;Josh Howarth&lt;/a&gt; | &lt;a href="https://en.wikipedia.org/wiki/Yahoo!"&gt;Yahoo!&lt;/a&gt; &amp;ndash;&amp;gt; Afifa Mushtaque | &lt;a href="https://en.wikipedia.org/wiki/Goldilocks_and_the_Three_Bears"&gt;Goldilocks and the Three Bears&lt;/a&gt; | &lt;a href="https://en.wikipedia.org/wiki/Psychology_Today"&gt;Psychology Today&lt;/a&gt; &amp;ndash;&amp;gt; &lt;a href="https://alexlickerman.com/"&gt;Alex Lickerman&lt;/a&gt; | &lt;a href="https://theinspirationallifestyle.com/"&gt;Dan Munro&lt;/a&gt; | &lt;a href="https://van.neist.at/"&gt;Van Neistat&lt;/a&gt; | &lt;a href="https://lisawilliamsphd.com/"&gt;Lisa Williams&lt;/a&gt; | &lt;a href="https://en.wikipedia.org/wiki/Zeno_of_Citium"&gt;Zeno of Citium&lt;/a&gt; | &lt;a href="https://www.ninertimes.com/"&gt;The Niner Times&lt;/a&gt; &amp;ndash;&amp;gt; Jessica Danesi | &lt;a href="https://en.wikipedia.org/wiki/The_Atlantic"&gt;The Atlantic&lt;/a&gt; &amp;ndash;&amp;gt; &lt;a href="https://annagoldfarb.com/"&gt;Anna Goldfarb&lt;/a&gt; | &lt;a href="https://en.wikipedia.org/wiki/PBS"&gt;PBS&lt;/a&gt; &amp;ndash;&amp;gt; &lt;a href="https://en.wikipedia.org/wiki/Paul_Solman"&gt;Paul Solman&lt;/a&gt; &amp;ndash;&amp;gt; &lt;a href="https://en.wikipedia.org/wiki/Joel_Waldfogel"&gt;Joel Waldfogel&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://indieweb.org/syndication-models"&gt;Syndication&lt;/a&gt;: &lt;a href="https://forum.agoraroad.com/index.php?threads/agora-road-travelogue-december.6246/post-96136"&gt;Agora Road&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id="comments"&gt;Comments &lt;a class="anchor" href="#comments"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Email: &lt;a href="mailto:hi@foreverliketh.is"&gt;hi@foreverliketh.is&lt;/a&gt;&lt;/p&gt;

&lt;div id="fastcomments-widget"&gt;&lt;/div&gt;

&lt;details&gt;
IndieWeb h-entry
&lt;article class="h-entry"&gt;
&lt;h1 class="p-name"&gt;Gift Giving&lt;/h1&gt;
&lt;p&gt;Published by &lt;a class="p-author h-card" href="https://foreverliketh.is/"&gt;foreverliketh.is&lt;/a&gt;
on &lt;time class="dt-published" datetime="2023-12-31 10:00:00"&gt;31&lt;sup&gt;st&lt;/sup&gt; December 2023&lt;/time&gt;&lt;/p&gt;
&lt;p class="p-summary"&gt;Ya Gotta Try&lt;/p&gt;
&lt;div class="e-content"&gt;
&lt;p&gt;Bah! Humbug!&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;a class="u-url" href="https://foreverliketh.is/blog/gift-giving/"&gt;Gift Giving&lt;/a&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;a class="u-syndication" href="https://forum.agoraroad.com/index.php?threads/agora-road-travelogue-december.6246/post-96136" rel="syndication"&gt;Agora Road&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a class="p-category" href="https://foreverliketh.is/categories/psychology/"&gt;Psychology&lt;/a&gt;&lt;/p&gt;
&lt;/article&gt;
&lt;/details&gt;</description><author>foreverliketh.is</author><pubDate>Sun, 31 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://foreverliketh.is/blog/gift-giving/</guid></item><item><title>Books I read in 2023... for fun!</title><link>https://swaits.com/books-read-this-year/</link><description>&lt;p&gt;I developed an interest in reading "just for fun" this year. In fact, I have
always had a passion for reading. However, I used to primarily read academic
papers and nonfiction books, particularly those of a technical nature.&lt;/p&gt;
&lt;p&gt;Earlier this year, I discovered the convenience of borrowing library books
through &lt;a href="https://libbyapp.com/" rel="external"&gt;Libby&lt;/a&gt; and being able to read them on my
Kindle. This revelation sparked a new reading journey. Throughout 2023, I
successfully cultivated a habit of reading for entertainment purposes, often
surpassing my reading for educational purposes.&lt;/p&gt;
&lt;p&gt;Below is a list of books I read for entertainment this year, excluding those
related to business, self-help, academia, and technical subjects.&lt;/p&gt;
&lt;p&gt;If you want to see my recommended list, most of which is nonfiction, check out
my top-level &lt;a href="https://swaits.com/books"&gt;books page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Note that this is just a list. These are not reviews. Also, &lt;strong&gt;the included
descriptions are taken from the publishers, and are not my own&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id="contents"&gt;Contents&lt;/h4&gt;
&lt;!--toc:start--&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://swaits.com/books-read-this-year/#nonfiction"&gt;Nonfiction&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://swaits.com/books-read-this-year/#killers-of-the-flower-moon-by-david-grann"&gt;Killers of the Flower Moon by David Grann&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/books-read-this-year/#standalone-fiction"&gt;Standalone Fiction&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://swaits.com/books-read-this-year/#camp-zero-by-michelle-min-sterling"&gt;Camp Zero by Michelle Min Sterling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/books-read-this-year/#hidden-pictures-by-jason-rekulak"&gt;Hidden Pictures by Jason Rekulak&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/books-read-this-year/#the-house-in-the-pines-by-ana-reyes"&gt;The House in the Pines by Ana Reyes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/books-read-this-year/#northern-spy-by-flynn-berry"&gt;Northern Spy by Flynn Berry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/books-read-this-year/#sea-of-tranquility-by-emily-st-john-mandel"&gt;Sea of Tranquility by Emily St. John Mandel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/books-read-this-year/#trust-by-hernan-diaz"&gt;TRUST by Hernan Diaz&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/books-read-this-year/#series-fiction"&gt;Series, Fiction&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://swaits.com/books-read-this-year/#crescent-city-series-by-sarah-j-maas"&gt;Crescent City series by Sarah J. Maas&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/books-read-this-year/#jack-reacher-series-by-lee-child"&gt;Jack Reacher series by Lee Child&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/books-read-this-year/#a-leaphorn-chee-novel-series-by-tony-hillerman"&gt;A Leaphorn &amp;amp; Chee Novel series by Tony Hillerman&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/books-read-this-year/#the-murderbot-diaries-series-by-martha-wells"&gt;The Murderbot Diaries series by Martha Wells&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/books-read-this-year/#wild-robot-series-by-peter-brown"&gt;Wild Robot series by Peter Brown&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;!--toc:end--&gt;
&lt;h2 id="nonfiction"&gt;Nonfiction&lt;/h2&gt;
&lt;h3 id="killers-of-the-flower-moon-by-david-grann"&gt;Killers of the Flower Moon by David Grann&lt;/h3&gt;
&lt;p&gt;I discovered this book when I saw a trailer for the
&lt;a href="https://en.wikipedia.org/wiki/Killers_of_the_Flower_Moon_(film)" rel="external"&gt;movie&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="Killers of the Flower Moon book cover" src="https://swaits.com/books-read-this-year/killers-flower-moon.jpeg" /&gt;&lt;/p&gt;
&lt;p&gt;In the 1920s, the richest people per capita in the world were members of the
Osage Nation in Oklahoma. After oil was discovered beneath their land, the
Osage rode in chauffeured automobiles, built mansions, and sent their
children to study in Europe.&lt;/p&gt;
&lt;p&gt;Then, one by one, the Osage began to be killed off. The family of an Osage
woman, Mollie Burkhart, became a prime target. One of her relatives was shot.
Another was poisoned. And it was just the beginning, as more and more Osage
were dying under mysterious circumstances, and many of those who dared to
investigate the killings were themselves murdered.&lt;/p&gt;
&lt;p&gt;As the death toll rose, the newly created FBI took up the case, and the young
director, J. Edgar Hoover, turned to a former Texas Ranger named Tom White to
try to unravel the mystery. White put together an undercover team, including
a Native American agent who infiltrated the region, and together with the
Osage began to expose one of the most chilling conspiracies in American
history.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="standalone-fiction"&gt;Standalone Fiction&lt;/h2&gt;
&lt;h3 id="camp-zero-by-michelle-min-sterling"&gt;Camp Zero by Michelle Min Sterling&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="Camp Zero book cover" src="https://swaits.com/books-read-this-year/camp-zero.jpeg" /&gt;&lt;/p&gt;
&lt;p&gt;In remote northern Canada, a team led by a visionary American architect is
break­ing ground on a building project called Camp Zero, intended to be the
beginning of a new way of life. A clever and determined young woman
code-named Rose is offered a chance to join the Blooms, a group hired to
entertain the men in camp—but her real mission is to secretly monitor the
mercurial architect in charge. In return, she’ll receive a home for her
climate-displaced Korean immigrant mother and herself.&lt;/p&gt;
&lt;p&gt;Rose quickly secures the trust of her target, only to discover that everyone
has a hidden agenda, and nothing is as it seems. Through skill­fully braided
perspectives, including those of a young professor longing to escape his
wealthy family and an all-woman military research unit struggling for
survival at a climate station, the fate of Camp Zero’s inhabitants reaches a
stunning crescendo.&lt;/p&gt;
&lt;p&gt;Atmospheric, fiercely original, and utterly gripping, Camp Zero is an
electrifying page-turner and a masterful exploration of who and what will
survive in a warming world, and how falling in love and building community
can be the most daring acts of all.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="hidden-pictures-by-jason-rekulak"&gt;Hidden Pictures by Jason Rekulak&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="Hidden Pictures book cover" src="https://swaits.com/books-read-this-year/hidden-pictures.jpeg" /&gt;&lt;/p&gt;
&lt;p&gt;Mallory Quinn is fresh out of rehab when she takes a job as a babysitter for
Ted and Caroline Maxwell. She is to look after their five-year-old son,
Teddy.&lt;/p&gt;
&lt;p&gt;Mallory immediately loves it. She has her own living space, goes out for
nightly runs, and has the stability she craves. And she sincerely bonds with
Teddy, a sweet, shy boy who is never without his sketchbook and pencil. His
drawings are the usual fare: trees, rabbits, balloons. But one day, he draws
something different: a man in a forest, dragging a woman’s lifeless body.&lt;/p&gt;
&lt;p&gt;Then, Teddy’s artwork becomes increasingly sinister, and his stick figures
quickly evolve into lifelike sketches well beyond the ability of any
five-year-old. Mallory begins to wonder if these are glimpses of a
long-unsolved murder, perhaps relayed by a supernatural force.&lt;/p&gt;
&lt;p&gt;Knowing just how crazy it all sounds, Mallory nevertheless sets out to
decipher the images and save Teddy before it’s too late.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="the-house-in-the-pines-by-ana-reyes"&gt;The House in the Pines by Ana Reyes&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="House in the Pines book cover" src="https://swaits.com/books-read-this-year/house-in-the-pines.jpeg" /&gt;&lt;/p&gt;
&lt;p&gt;Maya was a high school senior when her best friend, Aubrey, dropped dead in
front of the enigmatic man named Frank whom they’d been spending time with
all summer.&lt;/p&gt;
&lt;p&gt;Seven years later, Maya lives in Boston with a loving boyfriend and is
kicking the secret addiction that has allowed her to cope with what happened
years ago, the gaps in her memories, and the lost time that she can’t account
for. But her past comes rushing back when she comes across a recent YouTube
video in which a young woman suddenly keels over and dies in a diner while
sitting across from none other than Frank. Plunged into the trauma that has
defined her life, Maya heads to her Berkshires hometown to relive that
fateful summer—the influence Frank once had on her and the obsessive jealousy
that nearly destroyed her friendship with Aubrey.&lt;/p&gt;
&lt;p&gt;At her mother’s house, she excavates fragments of her past and notices hidden
messages in her deceased Guatemalan father’s book that didn’t stand out to
her earlier. To save herself, she must understand a story written before she
was born, but time keeps running out, and soon, all roads are leading back to
Frank’s cabin.&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;Utterly unique and captivating, The House in the Pines keeps you guessing
about whether we can ever fully confront the past and return home.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="northern-spy-by-flynn-berry"&gt;Northern Spy by Flynn Berry&lt;/h3&gt;
&lt;p&gt;&lt;img alt="Northern Spy book cover" src="https://swaits.com/books-read-this-year/northern-spy.jpeg" /&gt;&lt;/p&gt;
&lt;p&gt;A producer at the BBC and mother to a new baby, Tessa is at work in Belfast one
day when the news of another raid comes on the air. The IRA may have gone
underground in the two decades since the Good Friday Agreement, but they never
really went away, and lately bomb threats, security checkpoints, and helicopters
floating ominously over the city have become features of everyday life. As the
news reporter requests the public's help in locating those responsible for the
robbery, security footage reveals Tessa's sister, Marian, pulling a black ski
mask over her face.&lt;/p&gt;
&lt;p&gt;The police believe Marian has joined the IRA, but Tessa is convinced she must
have been abducted or coerced; the sisters have always opposed the violence
enacted in the name of uniting Ireland. And besides, Marian is vacationing on
the north coast. Tessa just spoke to her yesterday.&lt;/p&gt;
&lt;p&gt;When the truth about Marian comes to light, Tessa is faced with impossible
choices that will test the limits of her ideals, the bonds of her family, her
notions of right and wrong, and her identity as a sister and a mother. Walking
an increasingly perilous road, she wants nothing more than to protect the one
person she loves more fiercely than her sister: her infant son, Finn.&lt;/p&gt;
&lt;p&gt;Riveting, atmospheric, and exquisitely written, Northern Spy is at once a
heart-pounding story of the contemporary IRA and a moving portrait of sister-
and motherhood, and of life in a deeply divided society.&lt;/p&gt;
&lt;h3 id="sea-of-tranquility-by-emily-st-john-mandel"&gt;Sea of Tranquility by Emily St. John Mandel&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="Sea of Tranquility book cover" src="https://swaits.com/books-read-this-year/sea-of-tranquility.jpeg" /&gt;&lt;/p&gt;
&lt;p&gt;Edwin St. Andrew is eighteen years old when he crosses the Atlantic by
steamship, exiled from polite society following an ill-conceived diatribe at
a dinner party. He enters the forest, spellbound by the beauty of the
Canadian wilderness, and suddenly hears the notes of a violin echoing in an
airship terminal—an experience that shocks him to his core.&lt;/p&gt;
&lt;p&gt;Two centuries later a famous writer named Olive Llewellyn is on a book tour.
She’s traveling all over Earth, but her home is the second moon colony, a
place of white stone, spired towers, and artificial beauty. Within the text
of Olive’s best-selling pandemic novel lies a strange passage: a man plays
his violin for change in the echoing corridor of an airship terminal as the
trees of a forest rise around him.&lt;/p&gt;
&lt;p&gt;When Gaspery-Jacques Roberts, a detective in the black-skied Night City, is
hired to investigate an anomaly in the North American wilderness, he uncovers
a series of lives upended: The exiled son of an earl driven to madness, a
writer trapped far from home as a pandemic ravages Earth, and a childhood
friend from the Night City who, like Gaspery himself, has glimpsed the chance
to do something extraordinary that will disrupt the timeline of the universe.&lt;/p&gt;
&lt;p&gt;A virtuoso performance that is as human and tender as it is intellectually
playful, Sea of Tranquility is a novel of time travel and metaphysics that
precisely captures the reality of our current moment.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="trust-by-hernan-diaz"&gt;TRUST by Hernan Diaz&lt;/h3&gt;
&lt;p&gt;I went through a list of past Pulitzer Prize winners and I picked this one up.
Ultimately, I'm glad I read it. But, I am also a little less sure that looking
at Pulitzer Prize winners is a good idea.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="TRUST book cover" src="https://swaits.com/books-read-this-year/trust.jpeg" /&gt;&lt;/p&gt;
&lt;p&gt;Even through the roar and effervescence of the 1920s, everyone in New York
has heard of Benjamin and Helen Rask. He is a legendary Wall Street tycoon;
she is the daughter of eccentric aristocrats. Together, they have risen to
the very top of a world of seemingly endless wealth—all as a decade of excess
and speculation draws to an end. But at what cost have they acquired their
immense fortune? This is the mystery at the center of Bonds, a successful
1937 novel that all of New York seems to have read. Yet there are other
versions of this tale of privilege and deceit.&lt;/p&gt;
&lt;p&gt;Hernan Diaz’s TRUST elegantly puts these competing narratives into
conversation with one another—and in tension with the perspective of one
woman bent on disentangling fact from fiction. The result is a novel that
spans over a century and becomes more exhilarating with each new revelation.&lt;/p&gt;
&lt;p&gt;At once an immersive story and a brilliant literary puzzle, TRUST engages the
reader in a quest for the truth while confronting the deceptions that often
live at the heart of personal relationships, the reality-warping force of
capital, and the ease with which power can manipulate facts.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="series-fiction"&gt;Series, Fiction&lt;/h2&gt;
&lt;p&gt;I always read series in-order. Who doesn't? I think there are people that don't.&lt;/p&gt;
&lt;p&gt;Anyway, I list the books in each of these series that I completed.&lt;/p&gt;
&lt;h3 id="crescent-city-series-by-sarah-j-maas"&gt;Crescent City series by Sarah J. Maas&lt;/h3&gt;
&lt;p&gt;This book (and series) is consistently atop the best science fiction and
fantasty lists. A wee bit &lt;em&gt;romantasty&lt;/em&gt;, but here we go.&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Cover&lt;/th&gt;&lt;th&gt;Title&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;img alt="book cover" src="https://swaits.com/books-read-this-year/crescent-city-1.jpeg" /&gt;&lt;/td&gt;&lt;td&gt;House of Earth and Blood (Crescent City Book 1)&lt;/td&gt;&lt;td&gt;Bryce Quinlan had the perfect life-working hard all day and partying all night-until a demon murdered her closest friends, leaving her bereft, wounded, and alone. When the accused is behind bars but the crimes start up again, Bryce finds herself at the heart of the investigation. She'll do whatever it takes to avenge their deaths. &lt;br /&gt;&lt;br /&gt; Hunt Athalar is a notorious Fallen angel, now enslaved to the Archangels he once attempted to overthrow. His brutal skills and incredible strength have been set to one purpose-to assassinate his boss's enemies, no questions asked. But with a demon wreaking havoc in the city, he's offered an irresistible deal: help Bryce find the murderer, and his freedom will be within reach. &lt;br /&gt;&lt;br /&gt; As Bryce and Hunt dig deep into Crescent City's underbelly, they discover a dark power that threatens everything and everyone they hold dear, and they find, in each other, a blazing passion-one that could set them both free, if they'd only let it. &lt;br /&gt;&lt;br /&gt; With unforgettable characters, sizzling romance, and page-turning suspense, this richly inventive new fantasy series by #1 New York Times bestselling author Sarah J. Maas delves into the heartache of loss, the price of freedom-and the power of love.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id="jack-reacher-series-by-lee-child"&gt;Jack Reacher series by Lee Child&lt;/h3&gt;
&lt;p&gt;My in-laws watched the TV series for this and enjoyed it. So I decided to try
out the books. This is not deep stuff. But, I like the writing style, I like
the characters, and enjoy the adventures. You can call me low-brow or
something, I don't care. I blinked and boom, read half the series.&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Cover&lt;/th&gt;&lt;th&gt;Title&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;img alt="book cover" src="https://swaits.com/books-read-this-year/jack-reacher-1.jpeg" /&gt;&lt;/td&gt;&lt;td&gt;Killing Floor (Jack Reacher, Book 1)&lt;/td&gt;&lt;td&gt;Ex-military policeman Jack Reacher is a drifter. He’s just passing through Margrave, Georgia, and in less than an hour, he’s arrested for murder. Not much of a welcome. All Reacher knows is that he didn’t kill anybody. At least not here. Not lately. But he doesn’t stand a chance of convincing anyone. Not in Margrave, Georgia. Not a chance in hell.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img alt="book cover" src="https://swaits.com/books-read-this-year/jack-reacher-2.jpeg" /&gt;&lt;/td&gt;&lt;td&gt;Die Trying (Jack Reacher, Book 2)&lt;/td&gt;&lt;td&gt;Jack Reacher is an innocent bystander when he witnesses a woman kidnapped off a Chicago street in broad daylight. In the wrong place at the wrong time, he’s kidnapped with her. Chained together, locked in the back of a stifling van, and racing across America to an unknown destination for an unknown purpose, they’re at the mercy of a group of men demanding an impossible ransom. Because this mysterious woman is worth more than Reacher ever suspected. Now he has to save them both—from the inside out—or die trying....&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img alt="book cover" src="https://swaits.com/books-read-this-year/jack-reacher-3.jpeg" /&gt;&lt;/td&gt;&lt;td&gt;Tripwire (Jack Reacher, Book 3)&lt;/td&gt;&lt;td&gt;Ex military policeman Jack Reacher is enjoying the lazy anonymity of Key West when a stranger shows up asking for him. He’s got a lot of questions. Reacher does too, especially after the guy turns up dead. The answers lead Reacher on a cold trail back to New York, to the tenuous confidence of an alluring woman, and the dangerous corners of his own past.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img alt="book cover" src="https://swaits.com/books-read-this-year/jack-reacher-4.jpeg" /&gt;&lt;/td&gt;&lt;td&gt;Running Blind (Jack Reacher, Book 4)&lt;/td&gt;&lt;td&gt;Across the country, women are being murdered, victims of a disciplined and clever killer who leaves no trace evidence, no fatal wounds, no signs of struggle, and no clues to an apparent motive. They are, truly, perfect crimes. In fact, there’s only one thing that links the victims. Each one of the women knew Jack Reacher—and it’s got him running blind.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img alt="book cover" src="https://swaits.com/books-read-this-year/jack-reacher-5.jpeg" /&gt;&lt;/td&gt;&lt;td&gt;Echo Burning (Jack Reacher, Book 5)&lt;/td&gt;&lt;td&gt;Thumbing across the scorched Texas desert, Jack Reacher has nowhere to go and all the time in the world to get there. Cruising the same stretch of two-lane blacktop is Carmen Greer. For Reacher, the lift comes with a hitch. Carmen’s got a wild story to tell—all about her husband, her family secrets, and a hometown that’s purely gothic. She’s also got a plan. Reacher’s part of it. And before the sun sets, this ride could cost them both their lives.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img alt="book cover" src="https://swaits.com/books-read-this-year/jack-reacher-6.jpeg" /&gt;&lt;/td&gt;&lt;td&gt;Without Fail (Jack Reacher, Book 6)&lt;/td&gt;&lt;td&gt;Skilled, cautious, and anonymous, Jack Reacher is perfect for the job: to assassinate the vice president of the United States. Theoretically, of course. A female Secret Service agent wants Reacher to find the holes in her system, and fast—because a covert group already has the vice president in their sights. They’ve planned well. There’s just one thing they didn’t plan on: Reacher.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img alt="book cover" src="https://swaits.com/books-read-this-year/jack-reacher-7.jpeg" /&gt;&lt;/td&gt;&lt;td&gt;Persuader: A Jack Reacher Novel (#7)&lt;/td&gt;&lt;td&gt;Jack Reacher lives for the moment. Without a home. Without commitment. And with a burning desire to right wrongs—and rewrite his own agonizing past. DEA Susan Duffy is living for the future, knowing that she has made a terrible mistake by putting one of her own female agents into a death trap within a heavily guarded Maine mansion. &lt;br /&gt;&lt;br /&gt; Staging a brilliant ruse, Reacher hurtles into the dark heart of a vast criminal enterprise. Trying to rescue an agent whose time is running out, Reacher enters a crime lord’s waterfront fortress. There he will find a world of secrecy and violence—and confront some unfinished business from his own past.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img alt="book cover" src="https://swaits.com/books-read-this-year/jack-reacher-8.jpeg" /&gt;&lt;/td&gt;&lt;td&gt;The Enemy: A Jack Reacher Novel (#8)&lt;/td&gt;&lt;td&gt;Jack Reacher. Hero. Loner. Soldier. Soldier’s son. An elite military cop, he was one of the army’s brightest stars. But in every cop’s life there is one case that changes everything. For Jack Reacher, this is that case. &lt;br /&gt;&lt;br /&gt; New Year’s Day, 1990. In a North Carolina motel, a two-star general is found dead. His briefcase is missing. Nobody knows what was in it. Within minutes Reacher has his orders: Control the situation. Within hours the general’s wife is murdered. Then the dominoes really start to fall. &lt;br /&gt;&lt;br /&gt; Somewhere inside the vast worldwide fortress that is the U.S. Army, Reacher is being set up as a fall guy with the worst enemies a man can have. But Reacher won’t quit. He’s fighting a new kind of war—against an enemy he didn’t know he had. And against a conspiracy more chilling, ingenious, and treacherous than anyone could have guessed.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img alt="book cover" src="https://swaits.com/books-read-this-year/jack-reacher-9.jpeg" /&gt;&lt;/td&gt;&lt;td&gt;One Shot: A Jack Reacher Novel (#9)&lt;/td&gt;&lt;td&gt;Six shots. Five dead. One heartland city thrown into a state of terror. But within hours the cops have it solved: a slam-dunk case. Except for one thing. The accused man says: You got the wrong guy. Then he says: Get Reacher for me. &lt;br /&gt;&lt;br /&gt; And sure enough, ex—military investigator Jack Reacher is coming. He knows this shooter–a trained military sniper who never should have missed a shot. Reacher is certain something is not right–and soon the slam-dunk case explodes. &lt;br /&gt;&lt;br /&gt; Now Reacher is teamed with a beautiful young defense lawyer, moving closer to the unseen enemy who is pulling the strings. Reacher knows that no two opponents are created equal. This one has come to the heartland from his own kind of hell. And Reacher knows that the only way to take him down is to match his ruthlessness and cunning–and then beat him shot for shot.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img alt="book cover" src="https://swaits.com/books-read-this-year/jack-reacher-10.jpeg" /&gt;&lt;/td&gt;&lt;td&gt;The Hard Way: A Jack Reacher Novel (#10)&lt;/td&gt;&lt;td&gt;Jack Reacher was alone, the way he liked it, soaking up the hot, electric New York City night, watching a man cross the street to a parked Mercedes and drive it away. The car contained one million dollars in ransom money because Edward Lane, the man who paid it, would do anything to get his family back. &lt;br /&gt;&lt;br /&gt; Lane runs a highly illegal soldiers-for-hire operation. He will use any tool to find his beautiful wife and child. And Jack Reacher is the best manhunter in the world. &lt;br /&gt;&lt;br /&gt; On the trail of vicious kidnappers, Reacher learns the chilling secrets of his employer’s past . . . and of a horrific drama in the heart of a nasty little war. He knows that Edward Lane is hiding something. Something dirty. Something big. But Reacher also knows this: He’s already in way too deep to stop now. And if he has to do it the hard way, he will.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img alt="book cover" src="https://swaits.com/books-read-this-year/jack-reacher-11.jpeg" /&gt;&lt;/td&gt;&lt;td&gt;Bad Luck and Trouble: A Jack Reacher Novel (#11)&lt;/td&gt;&lt;td&gt;From a helicopter high above the California desert, a man is sent free-falling into the night. On the streets of Portland, Jack Reacher is pulled out of his wandering life and plunged into the heart of a conspiracy that is killing old friends . . . and the people he once trusted with his life. &lt;br /&gt;&lt;br /&gt; Reacher is the ultimate loner—no phone, no ties, no address. But a woman from his old military unit has found him using a signal only the eight members of their elite team would know. Then she tells him a terrifying story about the brutal death of a man they both served with. Soon Reacher is reuniting with the survivors of his team, scrambling to unravel the sudden disappearance of two other comrades. But Reacher won’t give up—because in a world of bad luck and trouble, when someone targets Jack Reacher and his team, they’d better be ready for what comes right back at them.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img alt="book cover" src="https://swaits.com/books-read-this-year/jack-reacher-12.jpeg" /&gt;&lt;/td&gt;&lt;td&gt;Nothing to Lose: A Jack Reacher Novel (#12)&lt;/td&gt;&lt;td&gt;Two small towns in the middle of nowhere: Hope and Despair. Between them, nothing but twelve miles of empty road. Jack Reacher can’t find a ride, so he walks. All he wants is a cup of coffee. What he gets are four hostile locals, a vagrancy charge, and an order to move on. They’re picking on the wrong guy. &lt;br /&gt;&lt;br /&gt; Reacher is a hard man. No job, no address, no baggage. Nothing at all, except hardheaded curiosity. What are the secrets that Despair seems so desperate to hide? &lt;br /&gt;&lt;br /&gt; With just one ally—a mysterious woman cop from Hope—and many enemies, Reacher goes up against a whole town, hunting the rich man at its core, cracking open his terrifying agenda, asking the question: Who has the edge—a man with everything to gain, or a man with nothing to lose?&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id="a-leaphorn-chee-novel-series-by-tony-hillerman"&gt;A Leaphorn &amp;amp; Chee Novel series by Tony Hillerman&lt;/h3&gt;
&lt;p&gt;I discovered this through an ad for the &lt;em&gt;Dark Winds&lt;/em&gt; TV series on AMC, which is
apparently based on this book. The ad was interesting, so I thought I'd give
the book a shot.&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Cover&lt;/th&gt;&lt;th&gt;Title&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;img alt="book cover" src="https://swaits.com/books-read-this-year/blessing-way.jpeg" /&gt;&lt;/td&gt;&lt;td&gt;The Blessing Way: A Leaphorn &amp;amp; Chee Novel #1&lt;/td&gt;&lt;td&gt;Homicide is always an abomination, but there is something exceptionally disturbing about the victim discovered in a high, lonely place—a corpse with a mouth full of sand—abandoned at a crime scene seemingly devoid of tracks or useful clues. Though it goes against his better judgment, Navajo Tribal Police Lieutenant Joe Leaphorn cannot help but suspect the hand of a supernatural killer. &lt;br /&gt;&lt;br /&gt; There is palpable evil in the air, and Leaphorn's pursuit of a Wolf-Witch leads him where even the bravest men fear, on a chilling trail that winds perilously between mysticism and murder.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id="the-murderbot-diaries-series-by-martha-wells"&gt;The Murderbot Diaries series by Martha Wells&lt;/h3&gt;
&lt;p&gt;I found this while browsing past Hugo/Nebula award winners in search of a new
science fiction novella. I could crank through quickly. I'm just about caught
up to the current-end of this series. Turned out to be a ton of fun.&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Cover&lt;/th&gt;&lt;th&gt;Title&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;img alt="book cover" src="https://swaits.com/books-read-this-year/murderbot-1.jpeg" /&gt;&lt;/td&gt;&lt;td&gt;All Systems Red: The Murderbot Diaries #1&lt;/td&gt;&lt;td&gt;&lt;em&gt;"As a heartless killing machine, I was a complete failure."&lt;/em&gt; &lt;br /&gt;&lt;br /&gt; In a corporate-dominated spacefaring future, planetary missions must be approved and supplied by the Company. Exploratory teams are accompanied by Company-supplied security androids, for their own safety. &lt;br /&gt;&lt;br /&gt; But in a society where contracts are awarded to the lowest bidder, safety isn’t a primary concern. &lt;br /&gt;&lt;br /&gt; On a distant planet, a team of scientists are conducting surface tests, shadowed by their Company-supplied ‘droid — a self-aware SecUnit that has hacked its own governor module, and refers to itself (though never out loud) as “Murderbot.” Scornful of humans, all it really wants is to be left alone long enough to figure out who it is. &lt;br /&gt;&lt;br /&gt; But when a neighboring mission goes dark, it's up to the scientists and their Murderbot to get to the truth.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img alt="book cover" src="https://swaits.com/books-read-this-year/murderbot-2.jpeg" /&gt;&lt;/td&gt;&lt;td&gt;Artificial Condition: The Murderbot Diaries #2&lt;/td&gt;&lt;td&gt;It has a dark past—one in which a number of humans were killed. A past that caused it to christen itself “Murderbot”. But it has only vague memories of the massacre that spawned that title, and it wants to know more. &lt;br /&gt;&lt;br /&gt; Teaming up with a Research Transport vessel named ART (you don’t want to know what the “A” stands for), Murderbot heads to the mining facility where it went rogue. &lt;br /&gt;&lt;br /&gt; What it discovers will forever change the way it thinks…&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img alt="book cover" src="https://swaits.com/books-read-this-year/murderbot-3.jpeg" /&gt;&lt;/td&gt;&lt;td&gt;Rogue Protocol: The Murderbot Diaries #3&lt;/td&gt;&lt;td&gt;&lt;em&gt;Who knew being a heartless killing machine would present so many moral dilemmas?&lt;/em&gt; &lt;br /&gt;&lt;br /&gt; Sci-fi’s favorite antisocial A.I. is back on a mission. The case against the too-big-to-fail GrayCris Corporation is floundering, and more importantly, authorities are beginning to ask more questions about where Dr. Mensah's SecUnit is. &lt;br /&gt;&lt;br /&gt; And Murderbot would rather those questions went away. For good.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img alt="book cover" src="https://swaits.com/books-read-this-year/murderbot-4.jpeg" /&gt;&lt;/td&gt;&lt;td&gt;Exit Strategy: The Murderbot Diaries #4&lt;/td&gt;&lt;td&gt;Murderbot wasn’t programmed to care. So, its decision to help the only human who ever showed it respect must be a system glitch, right? &lt;br /&gt;&lt;br /&gt; Having traveled the width of the galaxy to unearth details of its own murderous transgressions, as well as those of the GrayCris Corporation, Murderbot is heading home to help Dr. Mensah—its former owner (protector? friend?)—submit evidence that could prevent GrayCris from destroying more colonists in its never-ending quest for profit. &lt;br /&gt;&lt;br /&gt; But who’s going to believe a SecUnit gone rogue? &lt;br /&gt;&lt;br /&gt; And what will become of it when it’s caught?&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img alt="book cover" src="https://swaits.com/books-read-this-year/murderbot-5.jpeg" /&gt;&lt;/td&gt;&lt;td&gt;Network Effect: The Murderbot Diaries #5&lt;/td&gt;&lt;td&gt;&lt;em&gt;I’m usually alone in my head, and that’s where 90 plus percent of my problems are.&lt;/em&gt; &lt;br /&gt;&lt;br /&gt; When Murderbot's human associates (not friends, never friends) are captured and another not-friend from its past requires urgent assistance, Murderbot must choose between inertia and drastic action. &lt;br /&gt;&lt;br /&gt; Drastic action it is, then.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img alt="book cover" src="https://swaits.com/books-read-this-year/murderbot-6.jpeg" /&gt;&lt;/td&gt;&lt;td&gt;Fugitive Telemetry: The Murderbot Diaries #6&lt;/td&gt;&lt;td&gt;When Murderbot discovers a dead body on Preservation Station, it knows it is going to have to assist station security to determine who the body is (was), how they were killed (that should be relatively straightforward, at least), and why (because apparently that matters to a lot of people—who knew?) &lt;br /&gt;&lt;br /&gt; Yes, the unthinkable is about to happen: Murderbot must voluntarily speak to humans! &lt;br /&gt;&lt;br /&gt; Again!&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img alt="book cover" src="https://swaits.com/books-read-this-year/murderbot-7.jpeg" /&gt;&lt;/td&gt;&lt;td&gt;System Collapse: The Murderbot Diaries #7&lt;/td&gt;&lt;td&gt;Am I making it worse? I think I'm making it worse. &lt;br /&gt;&lt;br /&gt; Following the events in Network Effect, the Barish-Estranza corporation has sent rescue ships to a newly-colonized planet in peril, as well as additional SecUnits. But if there’s an ethical corporation out there, Murderbot has yet to find it, and if Barish-Estranza can’t have the planet, they’re sure as hell not leaving without something. If that something just happens to be an entire colony of humans, well, a free workforce is a decent runner-up prize. &lt;br /&gt;&lt;br /&gt; But there’s something wrong with Murderbot; it isn’t running within normal operational parameters. ART’s crew and the humans from Preservation are doing everything they can to protect the colonists, but with Barish-Estranza’s SecUnit-heavy persuasion teams, they’re going to have to hope Murderbot figures out what’s wrong with itself, and fast! &lt;br /&gt;&lt;br /&gt; Yeah, this plan is... not going to work.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id="wild-robot-series-by-peter-brown"&gt;Wild Robot series by Peter Brown&lt;/h3&gt;
&lt;p&gt;This is a middle-grade fiction series. I noticed my stepdaughter reading it and
I told her I'll also read whatever books she reads so we can talk about them
together. So, here we go!&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Cover&lt;/th&gt;&lt;th&gt;Title&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;img alt="book cover" src="https://swaits.com/books-read-this-year/wild-robot.jpeg" /&gt;&lt;/td&gt;&lt;td&gt;The Wild Robot (#1)&lt;/td&gt;&lt;td&gt;When robot Roz opens her eyes for the first time, she discovers that she is all alone on a remote, wild island. She has no idea how she got there or what her purpose is--but she knows she needs to survive. After battling a violent storm and escaping a vicious bear attack, she realizes that her only hope for survival is to adapt to her surroundings and learn from the island's unwelcoming animal inhabitants. &lt;br /&gt;&lt;br /&gt; As Roz slowly befriends the animals, the island starts to feel like home--until, one day, the robot's mysterious past comes back to haunt her. &lt;br /&gt;&lt;br /&gt; From bestselling and award-winning author and illustrator Peter Brown comes a heartwarming and action-packed novel about what happens when nature and technology collide.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;</description><author>swaits.com</author><pubDate>Sun, 31 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://swaits.com/books-read-this-year/</guid></item><item><title>Vendor Unlocking My Life</title><link>https://blakeashleyjr.com/posts/vendor-unlocking-my-life/</link><description>&lt;p&gt;In an era where digital dependence is commonplace, I moved towards personal tech independence in 2023. This blog post details my transition away from mainstream tech giants and towards more secure, private, and autonomous alternatives.&lt;/p&gt;
&lt;h2 id="breaking-free-from-the-norm"&gt;Breaking Free from the Norm&lt;/h2&gt;
&lt;p&gt;In 2023, I finally broke free from the likes of Google, Facebook, and other tech giants—a commitment I&amp;rsquo;d long made to myself but never fulfilled. Like many of you, Google and Facebook had been my internet cornerstones for years, offering convenience, cost-effectiveness, and entertainment.&lt;/p&gt;
&lt;p&gt;On a professional level, I&amp;rsquo;ve always been cognizant of the threat of &amp;lsquo;vendor lock-in&amp;rsquo;. How reliant am I on a single service, and what&amp;rsquo;s the fallout if it vanishes overnight?&lt;/p&gt;
&lt;p&gt;A personal turning point came with a widely-reported &lt;a href="https://web.archive.org/web/20231220165251/https://www.nytimes.com/2022/08/21/technology/google-surveillance-toddler-photo.html"&gt;incident&lt;/a&gt;: a father wrongfully accused by Google&amp;rsquo;s algorithms of possessing child sex abuse material in his private messages. This automated and erroneous finding led to the freezing of his account and immediate notification to law enforcement.&lt;/p&gt;
&lt;p&gt;Realizing the extent of my dependence on a few major players like Google was a wake-up call. My goal for 2023 was clear: to minimize my dependency on any single platform or service.&lt;/p&gt;



&lt;div class="newsletter-box"&gt;
&lt;form action="https://getsendstack.com/forms/a04fb31d-e4a0-48c4-9a3a-c3dac6c5a1b6/subscribers" class="newsletter-form" method="post"&gt;

    &lt;span style="display: none;"&gt;
        Success! Please verify your email address to confirm your subscription.
    &lt;/span&gt;

    &lt;ul style="display: none;"&gt;&lt;/ul&gt;

    &lt;p class="newsletter-heading"&gt;Subscribe to my newsletter&lt;/p&gt;
    &lt;div class="input-group flex"&gt;
        &lt;input class="email-input" name="email" type="email" /&gt;
        &lt;span class="input-group-btn"&gt;
          &lt;button class="btn btn-default" type="submit"&gt;Subscribe&lt;/button&gt;
          &lt;input name="tags" type="hidden" value="blog-form" /&gt;
        &lt;/span&gt;
    &lt;/div&gt;
      &lt;p class="newsletter-info"&gt;&lt;i&gt;No spam or sold emails. This is for blog posts and occasional updates.&lt;/i&gt;&lt;/p&gt;
  &lt;/form&gt;
&lt;/div&gt;



&lt;h2 id="improvements-i-made-this-year"&gt;Improvements I Made This Year:&lt;/h2&gt;
&lt;h3 id="emailcalendar"&gt;Email/Calendar&lt;/h3&gt;
&lt;p&gt;My first step was to address my email and calendar dependencies.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Migrated away from my 15-year-old Google account&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Switched to &lt;a href="https://proton.me/"&gt;Proton Mail&lt;/a&gt; for enhanced privacy and security.&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="https://blakeashleyjr.com/posts/vendor-unlocking-my-life/#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;Transitioned to using my own domains for email.&lt;/li&gt;
&lt;li&gt;Set up PGP using my own keys for secure communication.&lt;/li&gt;
&lt;li&gt;Moved scheduling to Cal.com for opensource scheduling.&lt;/li&gt;
&lt;li&gt;I setup &lt;a href="https://simplelogin.io/"&gt;SimpleLogin&lt;/a&gt; to use my own domain (a throwaway) for private email forwwarding. Should SimpleLogin disappear, I could keep all these emails flowing.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Todo:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Implement an automated &lt;a href="https://proton.me/support/proton-mail-export-tool"&gt;email backup system&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Figure out a solution for calendar synchronization.&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="https://blakeashleyjr.com/posts/vendor-unlocking-my-life/#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;Transition away from Google Photos (Currently locked-in due to family use).&lt;/li&gt;
&lt;li&gt;Permanently delete my Google account after a few more months of email forwarding.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="messaging"&gt;Messaging&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Moved (almost exclusively) to &lt;a href="https://signal.org/"&gt;Signal&lt;/a&gt; and &lt;a href="https://simplex.chat/"&gt;Simplex&lt;/a&gt; for private and secure messaging.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Successfully convinced many friends and family to switch to Signal.&lt;/li&gt;
&lt;li&gt;Using Simplex as a secure alternative to email for communications with strangers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stopped using Discord&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;I&amp;rsquo;ve enjoyed Matrix for quick questions and community building, despite not being a big fan of chatrooms for most use cases due to their lack of external searchability, synchronicity, etc.&lt;sup id="fnref:3"&gt;&lt;a class="footnote-ref" href="https://blakeashleyjr.com/posts/vendor-unlocking-my-life/#fn:3"&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Todo:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Get the remaining friends and family on Signal.&lt;/li&gt;
&lt;li&gt;Consider self-hosting my own Simplex infrastructure.&lt;/li&gt;
&lt;li&gt;Migrate to a self-hosted Matrix server.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="social-media"&gt;Social Media&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Embraced the fediverse by switching to &lt;a href="https://joinmastodon.org/"&gt;Mastodon&lt;/a&gt; and &lt;a href="https://join-lemmy.org/"&gt;Lemmy&lt;/a&gt;.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Deleted my Twitter, Reddit, Instagram, and Facebook accounts, prioritizing privacy and control over my digital social life.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Todo:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Plan to self-host my own Mastodon and Lemmy instances.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="web-hosting"&gt;Web Hosting&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Migrated to a static site powered by &lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt; for enhanced portability and control.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Todo:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Move away from Cloudflare hosting for greater independence.&lt;/li&gt;
&lt;li&gt;Enable Tor access to my site for increased privacy and accessibility.&lt;/li&gt;
&lt;li&gt;Self-host my newsletter system.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="homelab"&gt;Homelab&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Undertook a massive overhaul by moving dozens of services into a self-hosted k3s cluster and Ceph storage.&lt;/li&gt;
&lt;li&gt;Transitioned all my physical media into &lt;a href="https://jellyfin.org/"&gt;Jellyfin&lt;/a&gt;, supporting DRM-free content and direct purchases from creators.&lt;/li&gt;
&lt;li&gt;Installed security cameras around my home, ensuring they operate exclusively within my LAN and backups are encrypted.&lt;/li&gt;
&lt;li&gt;In the process of migrating all file storage to a blend of &lt;a href="https://nextcloud.com/"&gt;Nextcloud&lt;/a&gt; and &lt;a href="https://syncthing.net/"&gt;Syncthing&lt;/a&gt;, for a seamless and secure file management system.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="misc"&gt;MISC&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Now using VPN and Tor exclusively for web browsing, with very few exceptions (like banking).&lt;/li&gt;
&lt;li&gt;Sites that block Tor and/or VPN (including incessant CAPTCHA requirements) no longer receive my patronage.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="todo-in-2024"&gt;Todo in 2024&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Do a full account audit, going through my password manager and updating emails/passwords, adding 2FA, or deleting accounts when not in use.&lt;/li&gt;
&lt;li&gt;Plan to replace my current Pixel device, which has a locked bootloader, with an unlocked device running &lt;a href="https://grapheneos.org/"&gt;GrapheneOS.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Intend to replace my desktop and laptop with Nvidia GPUs for a setup that fully supports Wayland with AMD or Intel GPU, then move exclusively to a Linux desktop.&lt;/li&gt;
&lt;li&gt;Github still has an iron hold on my code. My plan is to self-host &lt;a href="https://forgejo.org/"&gt;Forgejo&lt;/a&gt; and mirror public repos to Github. I hope the &lt;a href="https://codeberg.org/forgejo/forgejo/issues/59"&gt;Activitypub movement&lt;/a&gt; can make its way to git and allow us to move off of centralized fit providers like Githubs.&lt;/li&gt;
&lt;li&gt;Switch as much of my financial life over to opensource, private, and decentralized currencies as possible. &lt;a href="https://www.getmonero.org/"&gt;Monero&lt;/a&gt; is my currency of choice.&lt;/li&gt;
&lt;li&gt;Break my dependence on YouTube, for personal and privacy reasons. I use privacy respecting frontends and clients, but the service still has a strong grip on me.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Stay tuned for more updates on my journey to tech independence and how you can do the same.&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Proton Mail is not perfect, because &lt;a href="https://www.privacyguides.org/en/basics/email-security/#what-is-the-web-key-directory-standard"&gt;email is not perfect&lt;/a&gt;. I do believe Proton does care about privacy and has made huge strides for consumers however. For paramount security, I use PGP to encrypt messages before they even hit the browser.&amp;#160;&lt;a class="footnote-backref" href="https://blakeashleyjr.com/posts/vendor-unlocking-my-life/#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Currently, I sync my calendars with &lt;a href="https://reclaim.ai/"&gt;reclaim.ai&lt;/a&gt;, but I&amp;rsquo;m seeking alternatives that don&amp;rsquo;t require a Google account.&amp;#160;&lt;a class="footnote-backref" href="https://blakeashleyjr.com/posts/vendor-unlocking-my-life/#fnref:2"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;I&amp;rsquo;ll elaborate on this later, but my preference for asynchronous communication stems from its ability to slow down life&amp;rsquo;s pace. Discord, in particular, poses privacy and centralization issues and lacks external searchability, making it inefficient for structured support or documentation.&amp;#160;&lt;a class="footnote-backref" href="https://blakeashleyjr.com/posts/vendor-unlocking-my-life/#fnref:3"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;

      
        &lt;p&gt;        
          Read more and join the conversation at &lt;a href="https://blakeashleyjr.com/posts/vendor-unlocking-my-life/"&gt;https://blakeashleyjr.com/posts/vendor-unlocking-my-life/&lt;/a&gt;
        &lt;/p&gt;
      &lt;/div&gt;</description><author>Blake Ashley Jr.</author><pubDate>Sat, 30 Dec 2023 19:24:13 GMT</pubDate><guid isPermaLink="true">https://blakeashleyjr.com/posts/vendor-unlocking-my-life/</guid></item><item><title>Movie review: The Vast of the Night</title><link>https://nicolaiarocci.com/movie-review-the-vast-of-the-night/</link><description>&lt;p&gt;Yesterday evening, we watched &lt;em&gt;The Vast of the Night&lt;/em&gt;, and what a pleasant
surprise it was.&lt;/p&gt;
&lt;p&gt;One night, in a small New Mexico town, a girl who works at a local radio station
and an older reporter boy listen to a recording of some strange noises. Through
the radio and its listeners, throughout a single night, they uncover a series of
sighting stories that, from clue to clue and radio testimony to radio testimony,
bring them close to uncovering something big.&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Sat, 30 Dec 2023 11:08:07 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/movie-review-the-vast-of-the-night/</guid></item><item><title>Bristlemouth Dev Kit Delayed - Customs Woes</title><link>https://blog.herlein.com/post/bristlemouth-delayed/</link><description>&lt;p&gt;I&amp;rsquo;ve been accepted into the &lt;a href="https://www.bristlemouth.org/pioneer"&gt;Bristlemouth Pioneer Program&lt;/a&gt;.  I&amp;rsquo;ve been granted a &lt;a href="https://www.sofarocean.com/products/spotter#s-subsurface"&gt;SOFAR Spotter&lt;/a&gt; with a &lt;a href="https://bristlemouth.notion.site/Developer-Kit-User-Guides-e9ca1b3c5a1c41c890d0105f2eb7c4b8"&gt;Development Kit&lt;/a&gt;.  Unfortunately it was detained in customs due to it&amp;rsquo;s high invoiced value.  I required a &amp;ldquo;customs agent&amp;rdquo; to clear it - which I did not have.&lt;/p&gt;</description><author>Greg Herlein</author><pubDate>Sat, 30 Dec 2023 10:00:01 GMT</pubDate><guid isPermaLink="true">https://blog.herlein.com/post/bristlemouth-delayed/</guid></item><item><title>Amazon's Silent Sacking</title><link>https://justingarrison.com/blog/2023-12-30-amazons-silent-sacking/</link><description>Companies are fighting back for quiet quitting and it's having a big impact.</description><author>Justin Garrison's Homepage</author><pubDate>Sat, 30 Dec 2023 09:29:31 GMT</pubDate><guid isPermaLink="true">https://justingarrison.com/blog/2023-12-30-amazons-silent-sacking/</guid></item><item><title>Summary : Mastermind Group Blueprint  by Tobe Brockner</title><link>https://www.chestergrant.com/summary-mastermind-group-blueprint-by-tobe-brockner</link><description>&lt;div class="posthaven-post-body"&gt;&lt;p&gt;&lt;/p&gt;&lt;center&gt;        &lt;div class="posthaven-gallery" id="posthaven_gallery[2092525]"&gt;
                  &lt;p class="posthaven-file posthaven-file-image posthaven-file-state-processed"&gt;
          &lt;img class="posthaven-gallery-image" src="https://phaven-prod.s3.amazonaws.com/files/image_part/asset/3125210/DOm_KWUO382z-8UtYbGDXEcxyW0/medium_cover.png" /&gt;
        &lt;/p&gt;

        &lt;/div&gt;
&lt;/center&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;1. To describe the Mastermind concept, let me give you a quick analogy. Let’s say that you have an apple and I have an apple. If we trade apples, you still only have one apple and I still only have one apple. But, let’s say that you and I both have an idea. If we exchange ideas, then you have two ideas and I have two ideas. That is, you have your original idea plus my idea, and I will have your idea plus my original idea.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;2. Hill stumbled upon this particular principle while researching the life of Henry Ford. He found that Ford met formally with a group of men who did not work at Ford Motor Company at least once per month with the intent of sharing ideas, solving problems, and identifying opportunities.&lt;/p&gt;&lt;p&gt;3. In this eclectic group, Ford found solace, like minds, and inspiration. When asked about the secret of his success, he said on several occasions that his monthly group meetings with these four men were pivotal in helping him reach the heights to which he arose.&lt;/p&gt;&lt;p&gt;4. Why You Should Join or Start a Mastermind Group:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The Ability to Work “on” Your Business Instead of Just “in” Your Business&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Camaraderie and Understanding - I have long said entrepreneurs are some of the loneliest people on the planet.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unique Perspectives&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Motivation - When you surround yourself with business people who are excited, passionate, and energetic about the challenges they’re presented with, you can’t help but feel the same way.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Accumulated Experience - No two people will ever see the same events the same way.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ability to Serve - I know it sounds cliché and it probably is, but it is still true: the more you give, the more you receive.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Accountability&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;5. It is much easier to recruit potential members when limiting group membership to only one company per industry. Think about your own industry. If you were in a group with one or two of your biggest competitors, would you be as open when it comes to sharing ideas and strategies? Probably not. So, do your best to find members from different industries.&lt;/p&gt;&lt;p&gt;6. Commitment - For the group to be as effective as possible, individual members must be committed to it. If attendance is sporadic or inconsistent, there will be subpar results. Besides that, it’s not really fair to other members if one or two don’t attend regularly. The whole point is to have a collaboration of ideas. When people aren’t there, collaboration suffers.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;7. Membership Dues One of the most effective ways to ensure group commitment is to charge membership dues. These can range from $25 up to $1,000 or more a month. There will be some costs involved in running a group such as food, beverages, handouts, printing costs, room rental, and marketing materials. Apart from having the funds to pay for these items, there is also a psychological reason for charging dues.&lt;/p&gt;&lt;p&gt;8. If someone is paying to be a part of your group, he or she is much more likely to stick with it and take it seriously. I currently charge $297 per month for members to join our Mastermind Group, and members rarely miss a meeting.&lt;/p&gt;&lt;p&gt;9. Charge whatever you want, but make sure it is enough to keep them committed.&lt;/p&gt;&lt;p&gt;10. Similar Situations - Although you should only have one person per industry in your group, you should also make sure that everyone is relatively similar in where they are in their business’s growth stage.&lt;/p&gt;&lt;p&gt;11. You don’t want a member who has a fifty-million-dollar, twenty-year-old company partnered with a company that has been in business for three years and is barely making five hundred thousand dollars annually.&lt;/p&gt;&lt;p&gt;12. Optimism and a Positive Attitude - It’s okay to plan for worst-case scenarios and create alternative plans, should your Plan A not work out, but there is no room for the eternal pessimist. Find members who have a generally positive outlook on life and business.&lt;/p&gt;&lt;p&gt;13. The pessimists of the world are fairly easy to spot, and you need to be on the lookout for them within your group. This is the guy (or gal) who complains about every little detail, from the venue to the food to the topics being presented.&lt;/p&gt;&lt;p&gt;14. Remember, one of the main benefits of the Mastermind Group is to be inspired and motivated by smart, sophisticated, passionate business owners. One bad apple can spoil the bunch, and nothing will make your group disintegrate faster than negativity.&lt;/p&gt;&lt;p&gt;15. If you encounter a pessimist in your group, you will need to take quick action to rectify the situation. I recommend first having a frank discussion with that person. If things do not improve, you may have to ask them to leave the group.&lt;/p&gt;&lt;p&gt;&lt;b&gt;16. Absolute Discretion It is critically important that you instill a grave sense of discretion among your Mastermind members. This goes beyond the whole “What happens in Vegas stays in Vegas” concept. Your members (and you) are going to be sharing intimately personal details about themselves, their employees, their competitors, their business practices, and their trade secrets. It is absolutely imperative that the group members treat this with the utmost respect and care. Trust is mandatory.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;17. My Mastermind members are required to sign nondisclosure agreements that have stiff violation penalties. Fortunately, I have never had to use any of the penalties against anyone, but the simple fact that I take it so seriously with my members goes a long way in fostering a culture of openness and trust. I strongly suggest you do something similar.&lt;/p&gt;&lt;p&gt;&lt;b&gt;18. Finally, traditional Mastermind Groups consist of no more than six people, each from different industries.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;19. Concentrate on keeping your group to a total of around four to six members, including you. Keep in mind that you can set up as many of these groups as you want, depending on the kind of time commitment you can make. My colleagues in other areas of the country are running anywhere from five to eight Mastermind meetings at once.&lt;/p&gt;&lt;p&gt;20. If you ask people to shell out $300 to $1,000 a month to attend your Mastermind Group, then you need to make it a GREAT event. Catered food is virtually mandatory.&lt;/p&gt;&lt;p&gt;21. The way you sell this thing to people is by making it an elite, exclusive club into which they are being invited. It’s not something just anyone can join. There is a screening process and possibly a waiting list. Reality is perception. Every little component—every little component—has to be congruent with this positioning, including the food. Maybe even especially the food.&lt;/p&gt;&lt;p&gt;22. There’s a time and a place for casual dining, but your Mastermind Group meeting is not one of those times or places.&lt;/p&gt;&lt;p&gt;23. Along with the food, make sure you provide plenty of drinks. I usually buy a few types of soda, juices, teas, and then also have plenty of bottled water on hand.&lt;/p&gt;&lt;p&gt;24. However, our traditional Mastermind Groups are held in a large conference room at a local credit union.&lt;/p&gt;&lt;p&gt;25. Venue layout is important to the overall flow of your meetings. For a smaller group (four to six members), a round-table-type layout tends to work well. You don’t want members sitting in rows where some sit behind others. For high group interaction, a layout like that is highly inconvenient. Members should sit in a circle or half circle where everyone is facing inward, towards everyone else, making group discussion much easier.&lt;/p&gt;&lt;p&gt;26. Any large room free from distractions will work. If in a large conference room, make sure you can close the doors to limit the amount of noise and distractions.&lt;/p&gt;&lt;p&gt;27. Other options for meeting rooms are hotel conference rooms, universities, public libraries, corporate offices, or executive suites. (Executive suites often provide a large conference room to its tenants, and these can sometimes be rented for less than one hundred dollars.) Look into various options, and you should be able to find an acceptable venue without too much trouble.&lt;/p&gt;&lt;p&gt;28. Depending on how many people are in the group, I will go over a marketing topic for the first thirty to forty-five minutes of the meeting. We then take a quick restroom break and start with the first group member. That group member then has about forty-five minutes to talk about three or four challenges they are having in their business. The whole group participates in making suggestions, giving ideas, or fleshing out concepts related to the particular challenges under discussion.&lt;/p&gt;&lt;p&gt;29. Someone, usually my assistant, will take notes of the interaction and create checklists, to-do lists, and action items for that member to complete during the next month. This gives that member something tangible to take home and implement in business during the next thirty days. These meetings are not just about theory or ideas. They are about action.&lt;/p&gt;&lt;p&gt;30. After the first member is finished, we move on to the second person and the cycle repeats itself.&lt;/p&gt;&lt;p&gt;&lt;b&gt;31. A lot of the real growth in the group comes from hearing about a successful strategy in one business or industry and then applying it to another business or industry, with only minor modifications. In this way, we all get to have a chance at shortcutting the learning curve, and more importantly, we get to implement something in our own businesses that has already been proven in another business.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;32. At the end of each meeting, I will usually take five to ten minutes to leave the group with a motivational or inspirational thought. This ends the meeting on a positive note and gets everyone excited to get back to work and start implementing their ideas through the next month until we meet again.&lt;/p&gt;&lt;p&gt;33. Every new member receives a copy of a welcome letter (a letter welcoming him or her to the group and providing the necessary contact information to get in touch with me), a copy of the nondisclosure agreement (which must be signed and turned in before the meeting starts), and a copy of the ground rules (which I will cover here).&lt;/p&gt;&lt;p&gt;34. Ground Rules for the Meeting:&lt;/p&gt;&lt;blockquote&gt;&lt;ul&gt;
&lt;li&gt;Attend Every Meeting - I am pretty firm about this one. If you can’t make a meeting, then you need to have a very good excuse. It’s just not fair to the rest of the group (or to you) to miss a meeting. I have actually instituted a “Two Strikes, You’re Out” policy, where if you miss two meetings during a calendar year, then you are gone.&lt;/li&gt;
&lt;li&gt;Stay for the Whole Meeting - The group is only as strong as the members’ willingness to give to one another.&lt;/li&gt;
&lt;li&gt;Silence/Turn Off Cell Phones - This is the one place where you give yourself permission not to be at the mercy of the daily distractions that plague you. The group needs everyone’s full attention, so turning off cell phones is a must.&lt;/li&gt;
&lt;li&gt;Actively Participate - If members just sit there without offering anything to the group, I will usually try to gently draw them into the discussion with a friendly, “Well we’ve heard from everyone so far except for you, Jack. What do you think about this problem?”&lt;/li&gt;
&lt;li&gt;Constructive Criticism Only/Be Positive&lt;/li&gt;
&lt;li&gt;Don’t Interrupt One Another&lt;/li&gt;
&lt;li&gt;Keep Your Comments Concise. Don’t Be a Time Hog.&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;&lt;p&gt;35. A basic meeting agenda will look like this: &lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;p&gt;9:00 a.m.–9:45 a.m. Presentation to Group &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;9:45 a.m.–9:50 a.m. Break &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;9:50 a.m.–10:30 a.m. Member 1 Speaks &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;10:30 a.m.–11:10 a.m. Member 2 Speaks &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;11:10 a.m.–11:15 a.m. Break &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;11:15 a.m.–11:55 a.m. Member 3 Speaks &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;11:55 a.m.–12:05 p.m. Lunch &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;12:05 p.m.–12:45 p.m. Member 4 Speaks &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;12:45 p.m.–1:00 p.m. Wrap Up/Q&amp;amp;A/Etc. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;36. This agenda assumes you have at least four members in your group. The times will have to be adjusted if there are more or fewer members.&lt;/p&gt;&lt;p&gt;37. Talk About During Their Allotted Time? The short answer is “whatever they want.”&lt;/p&gt;&lt;p&gt;38. In the event they need a little guidance to get started, ask them to come prepared with three challenges or opportunities they are facing right now.&lt;/p&gt;&lt;p&gt;39. Marketing generally isn’t sequential, but here is a basic process you can follow: identify, find, attract, and convert.&lt;/p&gt;&lt;p&gt;40. The biggest thing you want to look for are business owners who read a lot of self-help books, business books, and any other nonfiction works designed to help them improve their lives or business.&lt;/p&gt;&lt;p&gt;41. The second character trait you want to look for in a business owner is aggressive marketing. These entrepreneurs are the ones actively and aggressively trying to grow their business and aren’t afraid of spending money to do it.&lt;/p&gt;&lt;p&gt;42. Finally, you want to find those business owners who are somewhat established. Entrepreneurs involved with very young companies or startups are pinching every penny and may balk at spending money to join your group, even if it will benefit them.&lt;/p&gt;&lt;p&gt;43. Referrals: This is the best place to begin your efforts. Keep in mind that like attracts like. In other words, we tend to associate with and attract those who are similar to us. Even if you only have a few Mastermind members right now, you’ll want to start by getting them to refer their friends and colleagues to you.&lt;/p&gt;&lt;p&gt;44. Chances are, they are friends with and can positively influence potential members who would be a great fit for your group.&lt;/p&gt;&lt;p&gt;45. Members will come and members will go, so you will always need to keep your pipeline full of potential prospects.&lt;/p&gt;&lt;p&gt;&lt;b&gt;46. Keep this in mind when recruiting members for your Mastermind Group. You want those individuals who have a great work ethic and will be master implementers. Their natural enthusiasm for work will spread to the other group members and everyone in turn will benefit.&lt;/b&gt;&lt;/p&gt;&lt;/div&gt;</description><author>Chester Grant</author><pubDate>Sat, 30 Dec 2023 03:55:20 GMT</pubDate><guid isPermaLink="true">https://www.chestergrant.com/summary-mastermind-group-blueprint-by-tobe-brockner</guid></item><item><title>Estimate, target, plan and commit</title><link>https://tiberriver256.github.io/web%20development/estimate-target-plan-commit/</link><description>&lt;p&gt;I’ve started into
“&lt;a href="https://www.amazon.com/Software-Estimation-Demystifying-Developer-Practices/dp/0735605351"&gt;Software Estimation: Demystifying the Black Art&lt;/a&gt;”
by Steve McConnell, and of course… as is my style. I’m making some conjectures
about what I think software development should look like based off of reading
the first chapter. I am going to finish the book, but jotting some quick
thoughts down as I go helps me to digest the book as I go, so don’t be surprised
if I actually contradict something in the book. I haven’t read it all yet.&lt;/p&gt;

&lt;h2 id="some-definitions"&gt;Some definitions&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Estimate&lt;/strong&gt; - Something rather easily and simply generated that is roughly
accurate and expected to be off within a certain range. A simple formula that
may be good enough in most cases is suggested as
&lt;code class="language-plaintext highlighter-rouge"&gt;Effort = NumberOfRequirements * AverageEffortPerRequirement&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Target&lt;/strong&gt; - The business wants to do &lt;em&gt;something&lt;/em&gt; by &lt;em&gt;X&lt;/em&gt; date. A statement of
a desireable business outcome.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Plan&lt;/strong&gt; - This is a detailed schedule on how we plan to &lt;em&gt;hit&lt;/em&gt; said target (it
will likely include prioritization and leaving certain things out).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Commitment&lt;/strong&gt; - When someone or a team promises to hit the target with a
specific level of quality.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="a-logical-plan"&gt;A logical plan&lt;/h2&gt;

&lt;p&gt;You can expect the business to come to you with targets. We should be ready with
&lt;em&gt;good enough™&lt;/em&gt; estimates to help produce plans that we can commit to.&lt;/p&gt;

&lt;p&gt;So the sequence is:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Build a system that can be used to produce &lt;em&gt;good enough™&lt;/em&gt; estimates
on-demand&lt;/li&gt;
  &lt;li&gt;Wait for the business to bring you targets&lt;/li&gt;
  &lt;li&gt;Build plans to hit those targets using your estimates&lt;/li&gt;
  &lt;li&gt;Only build plans you’re comfortable committing to&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id="the-tricky-part"&gt;The tricky part&lt;/h2&gt;

&lt;p&gt;It seems to me that building a data-set to produce &lt;em&gt;good enough™&lt;/em&gt; estimates
on-demand is the tricky part. I believe Steve is going to cover this in more
detail later in the book but my guess as to some important details would be all
centered around measuring that &lt;code class="language-plaintext highlighter-rouge"&gt;AverageEffortPerRequirement&lt;/code&gt; and that the keys
to getting accurate measurements there are going to be around when can we say
something started and when can we say something is finished?&lt;/p&gt;

    &lt;p&gt;&lt;a href="https://tiberriver256.github.io/web%20development/estimate-target-plan-commit/"&gt;Estimate, target, plan and commit&lt;/a&gt; was originally published by Micah Rairdon at &lt;a href="https://tiberriver256.github.io"&gt;Tiberriver256&lt;/a&gt; on December 30, 2023.&lt;/p&gt;</description><author>Tiberriver256</author><pubDate>Sat, 30 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://tiberriver256.github.io/web%20development/estimate-target-plan-commit/</guid></item><item><title>Choose optimism</title><link>https://stephango.com/optimism</link><description>&lt;p&gt;Around the age of twenty-two I realized that my worldview had been deeply imbued with pessimism and cynicism. It was the culture I grew up in. A hostility to new ideas, to anything that strays from the norm. An assumption that if things &lt;em&gt;can&lt;/em&gt; go wrong, they &lt;em&gt;will&lt;/em&gt; go wrong — that &lt;a class="internal-link" href="/empathy"&gt;malice&lt;/a&gt; is pervasive.&lt;/p&gt;

&lt;p&gt;One day, I decided to become an optimist and life became much more fun.&lt;/p&gt;

&lt;p&gt;The life of a pessimist is easy but dreary. The life of an optimist is hard but exciting. Pessimism is easy because it costs nothing. Optimism is hard because it must be constantly reaffirmed. In the face of a hostile, cynical world, it takes effort to show that positivity has merit.&lt;/p&gt;

&lt;p&gt;To be an optimist, adopt these assumptions:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The future can be great&lt;/li&gt;
  &lt;li&gt;People’s intentions are mostly good&lt;/li&gt;
  &lt;li&gt;Ideas are fragile and need nurturing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every new idea is an unrealized dream. Dreams are delicate and easy to destroy. When an idea presents itself, try to imagine the best version of it — &lt;em&gt;what would make this idea great?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Pessimism and optimism share a trait: both are self-fulfilling. Your intention influences the outcome. Call it karma or, simply, effort. I would rather inhabit a future that has the possibility of being great.&lt;/p&gt;

&lt;p&gt;Only optimists can create a great future. Only optimists can imagine it. Only optimists will put in the effort to make it. If you want to create a great future, believe it can happen. Choose optimism.&lt;/p&gt;</description><author>Steph Ango</author><pubDate>Sat, 30 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://stephango.com/optimism</guid></item><item><title>Interactive demos using Astro</title><link>https://avikdas.com/2023/12/30/interactive-demos-using-astro.html</link><description>This blog is mostly text and images, but I’m a big fan of adding interactive components to make my explanations more effective. See my post on rendering curves in 3D as an example. For those interactive demos, the browser environment, with Javascript for the interactivity, is a fantastic delivery mechanism with wide reach and ease-of-use. Libraries like React make that easy, but a lot of the frameworks and tooling are built around the assumption that the end goal is a single-page app (SPA): the entire page is interactive, and page loads are handled by swapping out what’s on the page. Think Next.js, or the Vue equivalent, Nuxt.</description><author>Avik Das</author><pubDate>Sat, 30 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://avikdas.com/2023/12/30/interactive-demos-using-astro.html</guid></item><item><title>Books I read in 2023</title><link>https://nicolaiarocci.com/books-i-read-in-2023/</link><description>&lt;p&gt;I read 24 books for a total of 7070 pages in 2023. That&amp;rsquo;s seven more books than
last year, which is quite an outstanding result considering the seemingly
unstoppable decline in book reading &lt;a href="https://nicolaiarocci.com/my-favorite-books-of-2022/"&gt;I have suffered in recent
years&lt;/a&gt;. Most have been fiction books, and that&amp;rsquo;s
something new and influential with the final result, as I tend to read
non-fiction more slowly. The bad news is that I did not review most of the books
I read this year, and that sucks. The last review was in August, a &lt;a href="https://nicolaiarocci.com/a-few-late-book-reviews/"&gt;catch-up
review of several books&lt;/a&gt; clearly showing I was in
trouble.&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Fri, 29 Dec 2023 16:51:02 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/books-i-read-in-2023/</guid></item><item><title>Looking back at 2023</title><link>/2023/</link><description>&lt;p&gt;Last year I wrote &lt;a href="/2022/"&gt;“Looking back at 2022”&lt;/a&gt; and found it valuable to
reflect and celebrate a year of work, even if just as a reminder of the things
that happened before going into the next.&lt;/p&gt;

&lt;p&gt;So with another year having passed, time to try again! This time for my second
full year working at incident.io.&lt;/p&gt;

&lt;p&gt;To contextualise the year, some milestones (&lt;a href="https://incident.io/blog/reflecting-on-a-momentous-2023"&gt;see company post&lt;/a&gt;) are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;34 to 77 employees&lt;/li&gt;
  &lt;li&gt;Opened our first New York office, Stephen moving out to lead it&lt;/li&gt;
  &lt;li&gt;Shipped two new products: Status Pages and Catalog&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I always start these posts by listing everything that comes to mind over the
last year, finding common themes, then deciding on a structure to best
communicate them.&lt;/p&gt;

&lt;p&gt;This year we’ll go with:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="#the-work"&gt;The work&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href="#status-pages-jan-mar"&gt;Status Pages (Jan-Mar)&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href="#catalog-apr-july"&gt;Catalog (Apr-July)&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href="#redacted-july-dec"&gt;On-call &lt;del&gt;REDACTED&lt;/del&gt; (July-Dec)&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href="#wins"&gt;Wins&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href="#internships"&gt;Internships&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href="#migration-from-heroku-to-gcp"&gt;Migration from Heroku to GCP&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href="#engineering-nits"&gt;Engineering nits&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href="#observations"&gt;Observations&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href="#ai"&gt;AI&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href="#my-role"&gt;My role&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href="#writing"&gt;Writing&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href="#brace-for-impact"&gt;Brace for impact&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="the-work"&gt;The work&lt;/h2&gt;

&lt;p&gt;I’ve spent the last year working across three distinct teams, each working on
different (and totally greenfield) products.&lt;/p&gt;

&lt;h3 id="status-pages-jan-mar"&gt;Status Pages (Jan-Mar)&lt;/h3&gt;

&lt;p&gt;The year began with us wanting to launch incident.io’s first ‘new’ product,
&lt;a href="https://incident.io/status-pages"&gt;Status Pages&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In my last post I said:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I’ll be starting 2023 by supporting the creation of a new team, the first
we’ll be creating out of the general engineering pool.&lt;/p&gt;

  &lt;p&gt;In a past life, I’ve found success in joining a team and helping them
establish themselves, building relationships that last so I can support them
once I move away.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That team was the Status Pages team, and my gameplan for a project like this is
joining the team properly for 6-8 weeks, with an aim of:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Establish a baseline for ways of working (2 weeks)&lt;/li&gt;
  &lt;li&gt;Grind to a meaningful goal (2 weeks, repeat 1-2x)&lt;/li&gt;
  &lt;li&gt;Repeat (2) but with an eye on succession (2 weeks)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At least, this was how I framed it when planning with Pete…&lt;/p&gt;

&lt;p&gt;…and it – broadly – worked, with me joining a team of Martha (TL), Megan (PM),
Isaac, Dimitra (eng) and James (designer) to build the MVP of what has
eventually become our industry-leading status page offering.&lt;/p&gt;

&lt;figure&gt;
  &lt;img alt="Status Pages screenshot" src="/assets/images/2023-status-pages.webp" /&gt;
&lt;/figure&gt;

&lt;p&gt;As expected, the first step was figuring out what on earth we would build. It’s
easy to assume you ‘know’ what the project will be before you begin but that’s
always never the case: what even is a status page, really?&lt;/p&gt;

&lt;p&gt;Obviously, you need a page to display the status of your site, but:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Do we run health checks against customer sites?&lt;/li&gt;
  &lt;li&gt;Are we planning on integrating with monitoring tools like Datadog to power
uptime charts?&lt;/li&gt;
  &lt;li&gt;Will our pages be authenticated?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was the most difficult part of my time in the team, mostly because having
started work on January 1st, there were several key stakeholders out-of-office.&lt;/p&gt;

&lt;p&gt;We had assumed a status page was so ‘obvious’ that the team could get on with
planning the product without too much outside guidance, but it happens that
commercial goals significantly impact what you build in this space (are you
building for brand presence, existing customer experience, or revenue
generation?).&lt;/p&gt;

&lt;p&gt;This caused us to stall, which felt–to the team–like a false start. I’ll be keen
to avoid starting projects with similar unresolved questions in future!&lt;/p&gt;

&lt;p&gt;What followed was much smoother, though: &lt;a href="https://incident.io/blog/how-we-built-status-pages"&gt;learning our way around Next.js
SSR&lt;/a&gt; didn’t take too long, and smart decisions such as
using Vercel for hosting meant we got to an MVP very quickly. Our product teams
can be extremely productive when given space to execute and a clear goal; after
the initial ambiguity was cleared we went from an MVP to a feature-full and
polished page in just a couple of months.&lt;/p&gt;

&lt;p&gt;The team went on to &lt;a href="https://incident.io/blog/announcing-status-pages"&gt;launch Status Pages in April&lt;/a&gt; with
me having left in March. While I was sad to have left before we’d properly
launched the product, I’ve since been really proud of all the work the team has
produced.&lt;/p&gt;

&lt;p&gt;Especially considering my contribution is intended to be a solid foundation to
build on, I’m particularly happy at how much they innovated in the 8 months
after launch. Our Status Pages are now the most powerful on the market, with
options for internal or customer-specific pages, best-in-class integration with
the rest of our product, authenticated pages and more.&lt;/p&gt;

&lt;p&gt;I’m proud to have been there at the start to help them get going!&lt;/p&gt;

&lt;h3 id="catalog-apr-july"&gt;Catalog (Apr-July)&lt;/h3&gt;

&lt;p&gt;As I began to look for the next project after Status Pages, it happened that
customers kept asking us for ‘something’ that sounded a lot like a service
catalog.&lt;/p&gt;

&lt;p&gt;This landed on my doorstep, not least because &lt;a href="/service-registry"&gt;I’ve been thinking about service
catalogs for years&lt;/a&gt; but also because it’s a product feature
I’ve been planning since I joined the company. I’ve already written about my
experience of this project in &lt;a href="/catalog/"&gt;“Three months building a catalog”&lt;/a&gt;
where I talk about what the work itself looked like, so won’t repeat myself
here.&lt;/p&gt;

&lt;figure&gt;
  &lt;img alt="Catalog" src="/assets/images/2023-catalog.png" /&gt;
&lt;/figure&gt;

&lt;p&gt;Reflecting on this project from a personal perspective, though, I found the move
from Status Pages straight into catalog to be difficult.&lt;/p&gt;

&lt;p&gt;I’d left a team (Status Pages) that was just hitting its stride, a team I
enjoyed working with, and before the product I’d invested a lot into was
launched. It felt like I’d got off one stop too soon, and going straight back
into the (extreme) ambiguity of catalog discovery, initially solo, was a big
gear shift.&lt;/p&gt;

&lt;p&gt;Other aspects made this project hard, too: holidays and conferences came at
inopportune times, and it happens that building a catalog–at least, the one we
wanted to build–is very technically challenging. Even explaining what you were
building to internal teams was a hurdle!&lt;/p&gt;

&lt;p&gt;So hard work, but we &lt;a href="https://incident.io/blog/announcing-catalog"&gt;launched Catalog in June&lt;/a&gt; to a massive
amount of customer appreciation and surprisingly high adoption. The feedback we
heard was our product was much more flexible than our competitors, that it felt
great to use, and people were surprised at how many previously impossible or
painful automations it made possible.&lt;/p&gt;

&lt;p&gt;I stuck around until after launch this time, too, so I left with much more
closure than I had Status Pages.&lt;/p&gt;

&lt;h3 id="on-call-redacted-july-dec"&gt;On-call &lt;del&gt;REDACTED&lt;/del&gt; (July-Dec)&lt;/h3&gt;

&lt;p&gt;After catalog launched, I moved to start a team building our most ambitious
product yet, one that we’re yet to announce or launch.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;So happy to share this project was building &lt;a href="https://incident.io/on-call"&gt;incident.io
On-call&lt;/a&gt;!&lt;/p&gt;

  &lt;p&gt;You should absolutely check-out the launch video and keep an eye out for a
load of technical content I’ve been storing up while this project was
under-wraps that I can’t wait to get out in the open.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This project has required the largest amount of up-front design of any project
I’ve worked on before, the next largest (in my experience) being &lt;a href="/moving-on-from-gocardless/#gocardless-and-open-banking-dec-2020-july-2021"&gt;adding an
Open-Banking payments scheme to the GoCardless product&lt;/a&gt;, an
undertaking that built into four teams over a 9-month period.&lt;/p&gt;

&lt;p&gt;As an initial team of six (PM, TL, designer, 2x SDE, 1x SRE) the first two weeks
involved about 6 hours a day of whiteboarding and discussions, producing technical
docs to explore different approaches. The process reminded me a lot of Stripe’s
&lt;a href="https://stripe.com/blog/payment-api-design"&gt;designing a unified payments API&lt;/a&gt;, with us trying to consider every
possible way a customer might want to use this product before we commit to a
design.&lt;/p&gt;

&lt;p&gt;It was exhausting but a load of fun, and you’d often find the team sitting in
odd positions with blank stares after a day of working on it, or producing
increasingly unhinged AI photoshops and meeting notes…&lt;/p&gt;

&lt;figure&gt;
  &lt;img alt="Silly photos of the team" src="/assets/images/2023-unhinged.png" style="margin-bottom: 20px;" /&gt;
&lt;/figure&gt;

&lt;p&gt;After the initial discovery, we got stuck into building.&lt;/p&gt;

&lt;p&gt;This is a big project that we’re going to release in one go, which comes with
all the usual challenges like motivating the team without a regular “we’ve
shipped this!” cadence, or managing the compounding risk of building so much on
top of product that customers haven’t yet used.&lt;/p&gt;

&lt;p&gt;You can manage these problems: we’ve tried hard to find clear milestones and
celebrate hitting them, and we have design partners who are using what we build
and giving early feedback. It’s still harder than a normal 2-weeks-then-release
cycle though, especially in an organisation like ours that’s built around those
faster feedback loops.&lt;/p&gt;

&lt;p&gt;It’s awkward talking in abstract about what we’re building but I will say this
is something we knew we’d build from the moment I’d joined, and I’ve had it in
mind over every project I’ve ever done at incident. If I’ve done my job well,
the new product will fit within the rest of our product suite as if it was
always designed to be there (because in a sense, it was) and it’s been fun to
see which of those anticipatory decisions have paid-off.&lt;/p&gt;

&lt;p&gt;I can’t wait until we’ve launched and I can speak about this publicly, but for
now…&lt;/p&gt;

&lt;p&gt;🤐&lt;/p&gt;

&lt;h2 id="wins"&gt;Wins&lt;/h2&gt;

&lt;p&gt;There are a number of things I’d consider a ‘win’ from last year. Here are a few
that stood out to me.&lt;/p&gt;

&lt;h3 id="internships"&gt;Internships&lt;/h3&gt;

&lt;p&gt;It was very last-minute, but in December we decided to work with Imperial (my
old university) to offer three internship placements for the coming summer.&lt;/p&gt;

&lt;p&gt;My part in this process was to create a &lt;a href="https://github.com/incident-io/internships"&gt;take-home test&lt;/a&gt; that we’d
use to decide who to bring into the on-site interviews. I always find the
process of building these tests fun, and I was especially happy with how this
test introduced students to our space (incident response), having them build an
on-call scheduler.&lt;/p&gt;

&lt;figure&gt;
  &lt;img alt="Screenshot of the internship GitHub repo" src="/assets/images/2023-internships.png" style="margin-bottom: 20px;" /&gt;
  &lt;figcaption&gt;
    Taken from our internships repo &lt;a href="https://github.com/incident-io/internships"&gt;incident-io/internships&lt;/a&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The 20 candidates who passed the take-home came into the office for an
assessment day, interviewing with a number of our product team. We ended the day
extremely impressed with the quality of the candidates, leading to an intense
decision meeting where we had to decide which of the many candidates we wanted
to hire would get one of the three places.&lt;/p&gt;

&lt;p&gt;This process felt really special to me. I wouldn’t be where I am now had I not
done a similar six-month placement at GoCardless, and the idea that some of
these students may one day feel the same about incident.io is a great (but
weird!) moment.&lt;/p&gt;

&lt;h3 id="migration-from-heroku-to-gcp"&gt;Migration from Heroku to GCP&lt;/h3&gt;

&lt;p&gt;Since I joined incident we had run on Heroku. I’m happy to say that’s no longer
the case, having migrated ourselves entirely into GCP in September, taking less
than 10 minutes of downtime.&lt;/p&gt;

&lt;p&gt;I was around on migration day (Saturday) to assist, but full credit for how
smoothly this went goes to Ben Wheatley, our (at the time) one-man SRE team.&lt;/p&gt;

&lt;figure&gt;
  &lt;img alt="Slack messages from the incident channel" src="/assets/images/2023-migration.png" style="margin-bottom: 20px;" /&gt;
  &lt;figcaption&gt;
    Taken from &lt;a href="https://twitter.com/lawrjones/status/1700525812825427974"&gt;this tweet thread&lt;/a&gt; of the full migration process
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Ben had spent the weeks in advance of the migration both building out the GCP
platform and the migration plan into it. I’ve worked with Ben for years and know
how meticulous he is, but I was most impressed with how he worked with the
broader Product Engineering team during this process.&lt;/p&gt;

&lt;p&gt;For each stage of the migration, Ben found volunteers with an interest in
infrastructure to pair with him. Working like this was a great way to build
operational experience before we moved over (our Product Engineers are on-call
and will need this knowledge) but it also helped incorporate an app engineering
perspective, something you can see in the ergonomics of the new tools/processes.&lt;/p&gt;

&lt;p&gt;Not only did the migration go fast and smooth, it also showcased a model of SRE
&amp;lt;&amp;gt; SDE collaboration that is rare in our industry, one I’d like us to remember
and do more of as we grow.&lt;/p&gt;

&lt;h3 id="engineering-nits"&gt;Engineering nits&lt;/h3&gt;

&lt;p&gt;We try to be pragmatic when it comes to technical investments, accepting that no
code is ever ‘perfect’ and trying to avoid implementing ‘best practices’ without
asking if they make sense for us first.&lt;/p&gt;

&lt;p&gt;It’s easy to join the team and interpret this as “we never make technical
investments” which is a terrible outcome for everyone, especially as that’s
definitely &lt;em&gt;not&lt;/em&gt; our view.&lt;/p&gt;

&lt;p&gt;I believe the opposite: that we’re great at making judicious and impactful
technical investments and we’ve been doing so ever since the company was born.
On the hunch that this work is happening but lacked visibility, I created an
&lt;code class="language-plaintext highlighter-rouge"&gt;#engineering-nits&lt;/code&gt; Slack channel to improve things.&lt;/p&gt;

&lt;figure&gt;
  &lt;img alt="The #engineering-nits Slack channel" src="/assets/images/2023-engineering-nits.jpeg" style="margin-bottom: 20px;" /&gt;
&lt;/figure&gt;

&lt;p&gt;If–as an incident developer–you hit a snag or think “urgh, that was annoying”
when writing code or using tools, this channel is where you should post your
thoughts.&lt;/p&gt;

&lt;p&gt;It’s explicitly not about venting, and is instead about building awareness of
our weaknesses and highlighting the work people are doing to improve or fix
problems alongside normal product delivery.&lt;/p&gt;

&lt;p&gt;We’ve since had 10 engineers contribute 30 ‘nits’ to the channel, where we’ve
found solutions for 13 of them (45%). Every nit comes with a discussion that
shares context and history of the codebase, turning the channel into a great
knowledge-sharing tool as well as a demonstration of the investment we do want
to prioritise and an example of people doing it.&lt;/p&gt;

&lt;p&gt;These nits have ranged from:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Our hot-reloader is flaky and loading stale code (fixed!)&lt;/li&gt;
  &lt;li&gt;Writing ‘deletion protection’ code for the product is difficult (fixed!)&lt;/li&gt;
  &lt;li&gt;Deploys are slow (on-going)&lt;/li&gt;
  &lt;li&gt;Test output for errors is bad (fixed!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anyone is free to discuss or tackle the problems in the channel, and we’ve even
started a series of blog posts for people who want to share this type of work
externally (see &lt;a href="https://incident.io/blog/slack-previews"&gt;Storybook for Slack&lt;/a&gt; and &lt;a href="https://incident.io/blog/codegen-faster"&gt;Generating code
faster&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;At 25 engineers and growing, now is the time to start being deliberate about the
culture we want to create. Empowering every developer to own and improve the
codebase is a key part of my ideal culture; #engineering-nits is a small but
promising step in the right direction.&lt;/p&gt;

&lt;h2 id="observations"&gt;Observations&lt;/h2&gt;

&lt;p&gt;General observations that I’ve taken from the year, both general and personal.&lt;/p&gt;

&lt;h3 id="ai"&gt;AI&lt;/h3&gt;

&lt;p&gt;I began the year sceptical about AI. Clearly it was becoming an important
tool, but I didn’t know how quickly it would find its way into all software, or
how that would feel.&lt;/p&gt;

&lt;p&gt;After last year’s developments, I now believe AI tools will become a key part of
&lt;em&gt;every&lt;/em&gt; product team’s toolbelt, and teams who don’t take advantage of them will
be leaving a category of delight and product-polish on the table.&lt;/p&gt;

&lt;p&gt;I’ve come to this through several experiences, such as:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;My initial experiments using OpenAI APIs to build toy product features back in
February.&lt;/li&gt;
  &lt;li&gt;Watching how quickly teams at incident adopted these tools, finding new ways
to develop with them, such as incorporating real-time feedback from the
product into their evaluation of the model.&lt;/li&gt;
  &lt;li&gt;Recently using AI for data analysis of &lt;a href="https://incident.io/blog/festive-macbooks"&gt;developer MacBook build
times&lt;/a&gt; and was blown away at what it could do.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’m not yet convinced that AI will take over the world, and I don’t believe this
generation of AI tools is going to take our (software engineers) jobs. But I do
think you could review any software product and–with minimal effort–use AI tools
to improve or totally reimagine those UIs, leading to vastly improved customer
experiences.&lt;/p&gt;

&lt;p&gt;Just as the DevOps movement brought production operations into the remit of app
developers, the ubiquity of AI tools and the potential they unlock will force
the average developer to start using them. That’s going to be an interesting
shift, given how few developers have experience working with non-deterministic
data models or similar systems.&lt;/p&gt;

&lt;h3 id="my-role"&gt;My role&lt;/h3&gt;

&lt;p&gt;Urgh, this one is hard.&lt;/p&gt;

&lt;p&gt;For anyone in similar positions, let me be clear: moving from a Principal/Staff
position at a larger (500+) company into a start-up is going to feel pretty
confusing.&lt;/p&gt;

&lt;p&gt;I’ve had 2.5 years to figure this out but in a company that’s grown this
fast–from me joining as first employee to our now 80+ person team–how you
interpret your role might change every six months, so you don’t get much time to
settle in.&lt;/p&gt;

&lt;p&gt;This year started with the creation of the first separate team (Status Pages)
and was followed quickly by four more. We’ve built each team from a common
leadership structure: Product Manager, Tech Lead, Engineering Manager and (where
we have resource) a Design Lead.&lt;/p&gt;

&lt;p&gt;You need this structure to encourage the right type of inter-lead-and-team
collaboration, but it’s made my job a bit harder: as someone outside that
structure but with a role that requires me to work across teams, I have to try a
bit harder to make sure I’m involved at the right time. I still find my way into
much of what I need to be in and most people know when to pull on me for input,
but I miss the clearer structure I’d known at GoCardless where it was more
officially understood how I might be useful to people.&lt;/p&gt;

&lt;p&gt;On a different note: this year surprised me with how much time I spent actually
building.&lt;/p&gt;

&lt;figure&gt;
  &lt;img alt="GitHub contribution graph for 2023" src="/assets/images/2023-contributions.png" style="margin-bottom: 20px;" /&gt;
&lt;/figure&gt;

&lt;p&gt;Most of my time being spent in a team building things has meant a lot of my
impact has come from ensuring the product we’re building has ‘multiplicative
impact’ where each new product feature makes the whole suite stronger, in a
‘greater than the sum of its parts’ sense.&lt;/p&gt;

&lt;p&gt;The best example of this is the Catalog which has become a foundational part of
our product, weaving its way into many other product features. The idea of
putting your organisation’s data into our tool (via the Catalog) is to allow
other features to do things with that data, so it’s unsurprising other features
have begun to build on it, but it’s not just about that.&lt;/p&gt;

&lt;p&gt;Just as important as if other features start using it is making it easy for
teams to do so, providing ‘lego bricks’ through which they can access the
abstraction, and ensuring that if you invest in those ‘bricks’ then all other
features using them can benefit.&lt;/p&gt;

&lt;p&gt;Doing this right would mean Catalog ends up everywhere, which is what we’ve
seen:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://incident.io/changelog/auto-export-follow-ups"&gt;Auto-export follow-ups&lt;/a&gt;
can use Catalog to determine what Jira/Linear Project/Team you export to.&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://incident.io/changelog/introducing-status-page-sub-pages"&gt;Introducing status page sub-pages&lt;/a&gt;
links sub-pages to Catalog data so you can say “create sub-pages for each
Region in the Catalog”.&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://incident.io/changelog/configure-your-debriefs"&gt;Configure your debriefs&lt;/a&gt;
uses the Catalog to determine who to invite to your incident debriefs (e.g.
invite the TL + EM + PM of the Teams affected by this incident).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That even ignores much of what we’ve been building for the last 6 months (and is
yet to be released) that is built entirely on top of Catalog.&lt;/p&gt;

&lt;p&gt;The tl;dr is that this year has been about putting my energy into this type of
stewardship, both around the product that we build and the tools that we use to
build it. I expect next year will see me do less of this myself and more of
working with teams to help them think like this themselves, though it’ll depend
a lot on the challenges we face as to where I’m most useful.&lt;/p&gt;

&lt;h3 id="writing"&gt;Writing&lt;/h3&gt;

&lt;p&gt;Every year I say I’d like to write more, and each year I write less. No doubt
this trend will continue, but I am happy with a few pieces I wrote this year.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://incident.io/blog/monolith"&gt;Keep the monolith, but split the workloads&lt;/a&gt; started a huge
online discussion that kept going for a couple of weeks. This is great as the
post (1) exhibits a lot of ways we think about software at incident.io and (2)
gets us a bit more exposure as a Go shop, which is great for our recruitment
efforts.&lt;/p&gt;

&lt;p&gt;It’s also a topic I have a lot of experience with and one I’d love to speak
about, but am yet to find the right conference for it.&lt;/p&gt;

&lt;p&gt;The other big-ticket post was &lt;a href="https://incident.io/blog/monolith"&gt;Tracking developer build times to decide if the
M3 MacBook is worth upgrading&lt;/a&gt;. The investigation and process of
writing this post was really enjoyable, and like many of my favourites included a
combination of hacking (building the new hot-reloader), technical deep-dive (Go
compiler performance) and data analysis. It’s a great example of the ‘fun’
someone might have if they join the incident team.&lt;/p&gt;

&lt;p&gt;Personal blog posts are where I get a bit sad: this died a death this year,
mostly because I was spending all my efforts building product. There were a few
that landed well, though:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="/catalog/"&gt;Three months building Catalog&lt;/a&gt; (July)&lt;/li&gt;
  &lt;li&gt;&lt;a href="/state-machines/"&gt;Use your database to power state machines&lt;/a&gt; (September)&lt;/li&gt;
  &lt;li&gt;&lt;a href="/cas/"&gt;Adding concurrency control to HTTP APIs&lt;/a&gt; (October)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next year I’d love to lean a bit more technical, going deeper on the tools we
use at incident and the strategies we’ve taken to evolve the codebase or improve
reliability. We’ll see if there’s time for that, amongst everything else!&lt;/p&gt;

&lt;h2 id="brace-for-impact"&gt;Brace for impact&lt;/h2&gt;

&lt;p&gt;That’s it, 2023 is over with.&lt;/p&gt;

&lt;p&gt;While it’s been a hell of a year and I’m proud of everything in this post, I
confess it feels misrepresentative: the majority has been spent building a
product we’re yet to release, so it’s–by necessity–an incomplete picture.&lt;/p&gt;

&lt;p&gt;That’s no bad thing, though! It just means a lot of the work this year has gone
into making 2024 incredible.&lt;/p&gt;

&lt;p&gt;Unsurprisingly, I’m a mix of excited, hopeful, and trepidatious about the
next 12 months. I have no doubt it’ll be the biggest year yet in my incident.io
career, but I’m finding it harder to predict than the previous two. That’s the
nature of us making some big, potentially company-changing bets, but we won’t
know if we’ve hit a home run until they land.&lt;/p&gt;

&lt;p&gt;I know it’ll be a lot of hard work, but I’m confident we’ll make something great
of it. Whatever happens, I’ll be proud to work alongside the amazing team we’ve
built, am excited by all the new people we’ll welcome, and will try my best to
make sure this is a year we can’t forget.&lt;/p&gt;</description><author>Lawrence Jones</author><pubDate>Fri, 29 Dec 2023 14:00:00 GMT</pubDate><guid isPermaLink="true">/2023/</guid></item><item><title>📗 The Coming Wave, by Mustafa Suleyman</title><link>https://ernest.oppet.it/2023/12/29/the-coming-wave-by-mustafa-suleyman/</link><description>This book was so heavily promoted across the internet I had to read it. Just like how I succumbed to an Athletic Greens subscription after the deluge of podcast ads. Mustafa &amp;#8220;Moose&amp;#8221; Suleyman is particularly clued-in on AI and the regulation landscape. He&amp;#8217;s a co-founder of Deepmind and someone who is very plugged in to [&amp;#8230;]</description><author>Ernest Oppetit</author><pubDate>Fri, 29 Dec 2023 13:11:48 GMT</pubDate><guid isPermaLink="true">https://ernest.oppet.it/2023/12/29/the-coming-wave-by-mustafa-suleyman/</guid></item><item><title>Why My Print Didn't Output Before a Segmentation Fault</title><link>https://blog.yelinaung.com/posts/what-happened-to-my-print/</link><description>&lt;p&gt;So, as I was learning some &lt;a href="https://beej.us/guide/bgc/"&gt;C&lt;/a&gt;, at the pointers/segfault, I was trying this code myself.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-c"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #93a1a1; font-style: italic;"&gt;#include&lt;/span&gt; &lt;span style="color: #93a1a1; font-style: italic;"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span style="color: #93a1a1; font-style: italic;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #93a1a1; font-style: italic;"&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #859900; font-weight: bold;"&gt;int&lt;/span&gt; &lt;span style="color: #268bd2;"&gt;main&lt;/span&gt;(&lt;span style="color: #859900; font-weight: bold;"&gt;void&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt; &lt;span style="color: #268bd2;"&gt;printf&lt;/span&gt;(&lt;span style="color: #2aa198;"&gt;"%s"&lt;/span&gt;, &lt;span style="color: #2aa198;"&gt;"Hello!"&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt; &lt;span style="color: #859900; font-weight: bold;"&gt;int&lt;/span&gt; *&lt;span style="color: #268bd2;"&gt;p&lt;/span&gt; = &lt;span style="color: #cb4b16;"&gt;NULL&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt; *&lt;span style="color: #268bd2;"&gt;p&lt;/span&gt; = &lt;span style="color: #2aa198; font-weight: bold;"&gt;5&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt; &lt;span style="color: #93a1a1; font-style: italic;"&gt;// Will not be reached due to crash above
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #93a1a1; font-style: italic;"&gt;&lt;/span&gt; &lt;span style="color: #268bd2;"&gt;printf&lt;/span&gt;(&lt;span style="color: #2aa198;"&gt;"%s"&lt;/span&gt;, &lt;span style="color: #2aa198;"&gt;"Another Hello!"&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then compile it and run it in the terminal.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-fallback"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;$ gcc -Wall -Wextra -o hello hello.c &amp;amp;&amp;amp; ./hello
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;Segmentation fault (core dumped)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To my surprise, it did not print out both &amp;ldquo;Hello!&amp;rdquo; and &amp;ldquo;Another Hello!&amp;rdquo; and the program just crashed with &amp;ldquo;Segmentation fault (core dumped)&amp;rdquo; (aka segfault).&lt;/p&gt;</description><author>Ye Lin's Random stuff</author><pubDate>Fri, 29 Dec 2023 09:51:12 GMT</pubDate><guid isPermaLink="true">https://blog.yelinaung.com/posts/what-happened-to-my-print/</guid></item><item><title>Summary : How to Host a Party and Handle House Guests By Judith Brown</title><link>https://www.chestergrant.com/summary-how-to-host-a-party-and-handle-house-guests-by-judith-brown</link><description>&lt;div class="posthaven-post-body"&gt;&lt;p&gt;&lt;/p&gt;&lt;center&gt;        &lt;div class="posthaven-gallery" id="posthaven_gallery[2091988]"&gt;
                  &lt;p class="posthaven-file posthaven-file-image posthaven-file-state-processed"&gt;
          &lt;img class="posthaven-gallery-image" src="https://phaven-prod.s3.amazonaws.com/files/image_part/asset/3124608/SuVxil3MeSeeLoBRQDECnp--LfU/medium_cover.png" /&gt;
        &lt;/p&gt;

        &lt;/div&gt;
&lt;/center&gt;&lt;b&gt;1. Keep a record of the parties and get-togethers you've given, along with guest lists and menus and what aspects of the event were-or weren't-a success. It will make useful reading next time you plan to entertain.&lt;/b&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;2. If you plan a menu of limited courses, everything can be set out buffet style. That way you'll be able to enjoy the meal along with your guests, instead of running back and forth to serve.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;3. Remember to keep non-alcoholic drinks- juices, soda, etc.-on hand for guests who don't drink alcohol.&lt;/p&gt;&lt;p&gt;4. An easy and pleasurable way to entertain is to invite friends to your home for a relaxing Sunday brunch.&lt;/p&gt;&lt;p&gt;5. Invite your friends to a make-a-pizza party and see how creative you all can be.&lt;/p&gt;&lt;p&gt;6. Don't try out a new recipe on guests. Prepare it first for family members or close friends.&lt;/p&gt;&lt;p&gt;7. Make your buffet convenient for you and your guests by wrapping place settings of silverware in napkins and placing them in a basket.&lt;/p&gt;&lt;p&gt;8. Ice will last longer for a party if you first freeze an inch of water in the bottom of the ice bucket.&lt;/p&gt;&lt;p&gt;&lt;b&gt;9. Your guests will always remember your parties if you send them picture postcards of themselves. As each person enters the party, snap a photo. But only send flattering photographs.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;10. For a festive holiday get-together, have a Christmas cookie exchange. Invite neighbors and friends and ask them to bring some of the favorite cookies.&lt;/p&gt;&lt;p&gt;11. Holiday shopping won't seem a hassle if you can schedule your shopping excursions at off hours: right after the stores open, in mid-afternoon (2:30-4:30 P.M.), or late enough in the evening to avoid the after-work crowd. Try to avoid the crowds on weekends and lunch hours.&lt;/p&gt;&lt;p&gt;&lt;b&gt;12. You'll always have little gifts on hand if you remember to buy doubles of things you need yourself. Such items as jellies and preserves, correspondence notes, and toiletries make suitable gifts. You can also take advantage of sale items and specials that look like suitable standby gifts.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;13. Make a loaf of bread and wrap it up along with a jar of homemade preserves as a delicious and practical gift.&lt;/p&gt;&lt;/div&gt;</description><author>Chester Grant</author><pubDate>Fri, 29 Dec 2023 03:48:52 GMT</pubDate><guid isPermaLink="true">https://www.chestergrant.com/summary-how-to-host-a-party-and-handle-house-guests-by-judith-brown</guid></item><item><title>Reflecting on 2023, preparing for 2024</title><link>https://ntietz.com/blog/reflecting-on-2023-preparing-for-2024/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;This is one of those cliched posts:
Reflection on the year that's ending, reviewing last year's goals, and talking about hopes and goals for next year.
They're cliche, and they're also useful.
The planning and reflecting process is a useful one, and sharing openly means other people can come along and learn with me&lt;sup class="footnote-reference" id="fr-repeated-from-last-year-1"&gt;&lt;a href="https://ntietz.com/blog/reflecting-on-2023-preparing-for-2024/#fn-repeated-from-last-year"&gt;[1]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h1 id="reflecting-on-2023"&gt;Reflecting on 2023&lt;/h1&gt;
&lt;p&gt;I thought last year was action-packed and, uh, this year has kind of set the new bar.
It was literally a transformative year for me, but in the way of butterflies: I'm becoming the person I am meant to be.
I'm going to list professional things first, then personal things, then community and broader events.&lt;/p&gt;
&lt;h2 id="professional"&gt;Professional&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;I was promoted to Principal Software Engineer.&lt;/strong&gt;
Early in 2023 I was promoted from Senior Staff to Principal.
While our team size is smaller than it once was, this has still resulted in a notable shift in my responsibilities.
It has taken me some time to fully get my feet under me, but I've enjoyed the process and the new role.
Most notably, the shift is that I do a lot more cross-functional leadership (working with our customer-facing and business teams a lot more now) and I'm also the main technical advisor for our CTO.
I am a full member of our company leadership team—the only individual contributor in that meeting—and I'm able to bring a unique perspective as both a tech-focused leader and as the longest-tenured employee of the company.
It's been fun, and 2024 is going to be even better.&lt;/p&gt;
&lt;p&gt;I also had some fun technical things at work.
Of the things I can talk about, I wrote our first Rust production code and released &lt;a href="https://yarr.fyi"&gt;a quick introduction to Rust&lt;/a&gt; to help my coworkers learn Rust more quickly.
It's been a good experience getting Rust into production, and I have a good fun project with it in 2024, too!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I wrote a &lt;em&gt;lot&lt;/em&gt; with some hits in there.&lt;/strong&gt;
I set out the year with the goal of writing at least one post every two weeks as a sustainable rhythm.
I overshot this and wrote 56 blog posts, more than one per week!
The total word count for the year, including this post, is over 60,000.&lt;/p&gt;
&lt;p&gt;This has been a lot of work—yet at the same time, it doesn't feel like work at all.
At some point, &lt;em&gt;I want this sort of writing to be part of my livelihood&lt;/em&gt; but I'm deeply afraid of sucking the joy out of it by making it commercial.
There is a balance I can find, and this blog will never be commercial, but the motions of writing and creativity can turn into other opportunities.
For now, I'm keeping on with my writing and keeping my eyes open.&lt;/p&gt;
&lt;p&gt;Here are some of the hits from the year that got the most views:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ntietz.com/blog/write-more-useless-software/"&gt;Write more "useless" software&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ntietz.com/blog/forefront-of-innovation/"&gt;A student asked how I keep us innovative. I don't.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ntietz.com/blog/name-your-projects-cutesy-things/"&gt;Name your projects cutesy things&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ntietz.com/blog/throw-away-your-first-draft/"&gt;Throw away your first draft&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ntietz.com/blog/introducing-hurl/"&gt;Introducing Hurl&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But I also wrote some pieces that were just deeply personally meaningful.
In particular, the &lt;a href="https://ntietz.com/blog/digital-vigil-for-tdor/"&gt;digital vigil for Trans Day of Remembrance&lt;/a&gt; is some of my most important software I've written, I think.&lt;/p&gt;
&lt;p&gt;I'm not into ranking what I've written, and if I want to make a go of it as a business maybe it would be worth analyzing what was "successful" and what wasn't.
But from a personal perspective, I'm pretty happy with everything I wrote this year and I'm &lt;em&gt;deeply&lt;/em&gt; proud of the amount that I got done this year.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Released a programming language, Hurl!&lt;/strong&gt;
I finished working through &lt;a href="https://craftinginterpreters.com/"&gt;Crafting Interpreters&lt;/a&gt; at the beginning of the year.
The idea for &lt;a href="https://hurl.wtf"&gt;Hurl&lt;/a&gt; was bouncing around my head after conversations with a couple of people, and eventually it did come into reality this year.
Its launch post was one of my more popular posts, and I think there's something to that.
It was a serious implementation of a joke idea, and that sort of whimsy and humor is a foil to the deep seriousness that our industry tries to project.
I think we need more deeply &lt;em&gt;unserious&lt;/em&gt; software, we need more play.
I might even say we should write more useless software.&lt;/p&gt;
&lt;p&gt;Before this year, I didn't think I could do programming language stuff.
It seemed a dark art more mysterious to me than even operating system stuff.
Now I see that it's possible and not that bad: despite the tremendous depth, you can get started simply and then keep learning and playing.
Next year I'm going to do a little more with PL (&lt;em&gt;not&lt;/em&gt; with Hurl, but something nicer), but in balance with other interests.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;My productivity was high, and I often didn't see that.&lt;/strong&gt;
This year I started to realize that yeah, I'm quite productive even if I don't see it.
For fellow Recursers, this is something that they may be relieved is finally sinking in, since my brand during my batch was posts where I lamented I got nothing done then had a laundry list of accomplishments.
Now I'm starting to see my own productivity and separate "what I did" from "what I wanted to do," and that I need external mechanisms to &lt;em&gt;remember&lt;/em&gt; what I did.
Not getting everything done doesn't mean I wasn't productive, it just means my goals are quite ambitious or I got &lt;em&gt;other&lt;/em&gt; things done instead.&lt;/p&gt;
&lt;p&gt;A few things that I did this year while feeling "unproductive":&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Implemented &lt;a href="https://tdor.xyz"&gt;a digital vigil&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Created &lt;a href="https://hurl.wtf"&gt;a programming language&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Made a functioning web app for managing chess clubs (it works, project is on hold though!)&lt;/li&gt;
&lt;li&gt;Made a &lt;a href="https://ntietz.com/blog/sketch-chess-piece-trails/"&gt;visualization&lt;/a&gt; of the 2023 FIDE World Chess Championship games this year&lt;/li&gt;
&lt;li&gt;Wrote a &lt;a href="https://ntietz.com/blog/happy-pi-day-2023/"&gt;simulation of approximating pi with a cake&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Implemented RSA in &lt;a href="https://crates.io/crates/cryptoy"&gt;a toy cryptography crate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Wrote a &lt;a href="https://yarr.fyi/"&gt;crash course on Rust&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, notably, half of those were in the last few months when I got other things in my life in control!
I did have times where I got less done in my personal time, and times when I got more done.
That's pretty normal.
I'm really proud of what I've done this year!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;LLMs happened, and my relationship with them has changed.&lt;/strong&gt;
In 2022, I was deeply skeptical of LLMs and their power.
In 2023, I saw some incredibly impressive demos which showed that (1) they're pretty useful but more importantly (2) they're here to stay.
As a result, I leaned into learning how to use them.
If they're here to stay, I need to adapt and get used to them, right?&lt;/p&gt;
&lt;p&gt;I've used ChatGPT, Copilot, Claude, and other LLM tools to assist with my work as a programmer&lt;sup class="footnote-reference" id="fr-not-my-writing-1"&gt;&lt;a href="https://ntietz.com/blog/reflecting-on-2023-preparing-for-2024/#fn-not-my-writing"&gt;[2]&lt;/a&gt;&lt;/sup&gt;.
They're okay.
There's a lot they do well, and some sharp edges and fun failure modes, and this is all better discussed elsewhere.
And now I've largely shifted away from using them much beyond rare Copilot usage.&lt;/p&gt;
&lt;p&gt;I'm pretty content that I can learn to use them quite effectively if I need to in the future.
I'm not sure I'll want to.
In my experiments with them, using them daily for much of my work sucked a lot of the joy out of it.
And I'm not convinced it was a net productivity boost for me, in no small part because of how my brain works and its peculiarities.&lt;/p&gt;
&lt;h2 id="personal"&gt;Personal&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Oh hi, I'm a woman!&lt;/strong&gt;
This year I came into my identity as a trans woman and began my transition.
I'm fortunate to have a lot of support: my family, my work environment, the town I live in, and Recurse Center, all these communities have supported me.
It's a long road ahead, and so far it has been on whole a very healing and good process.
Life is much more enjoyable now.
Transition is sometimes painful, but also necessary and worth it.
I've found in myself a surprisingly extroverted woman, and it's been a surreal experience.&lt;/p&gt;
&lt;p&gt;I'm so happy now.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I've hit my best mental health in a long time.&lt;/strong&gt;
The end of 2021 / start of 2022 saw me in my deepest depression I can recall (at least along certain axes).
In contrast, I've come out of 2023 in my best mental state in a while, prepared to deal with what life throws at me.
There are a lot of aspects to this.
It's a combination of transition, therapy, and other psychiatric care.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Transition has improved my mental state.&lt;/strong&gt; It may be self-explanatory, but it turns out that a major underlying stressor like unrecognized and unaddressed gender dysphoria can mess a girl up. My depression in 2021/2022 was pretty strongly related to gender issues, and some of the first insights leading to my transition were from therapy sessions which helped me pull out of that depressive episode.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Therapy has been tremendously helpful.&lt;/strong&gt; It took a few tries with a few therapists, but I've once again found therapy to be helpful and this time am in it for the long-haul. It's expensive, and it's a necessary life expense for me. My therapist has been helping me grow into my emotions, learn how to understand and process them, understand myself and others, and process some of what life throws at me. We've worked on skills for dealing with all of this, and for dealing with acute situations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I've been diagnosed with ADHD.&lt;/strong&gt; This is one I've suspected for a while, and it is empowering to have a diagnosis and it's &lt;em&gt;life changing&lt;/em&gt; to be treated for it. My brain works in a different way when medicated, and it's improving both my work and my home life. It's a lot easier to do activities with the kids when I'm not either distracted by every single thing or deep in a hyperfocus rabbithole. And when the medication has worn off, I have retained larger reserves of energy from the day by not fighting my brain, so unmedicated times are also better.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Got my physical health in order, too.&lt;/strong&gt;
In 2022, I had some bad RSI-induced nerve pain in my arms.
For some portion of the year, both at work and at Recurse Center, I could not type without great pain.
I was limited to only typing in passwords, and all coding was done by voice using Talon.
I recovered from that and went back to my old habits.&lt;/p&gt;
&lt;p&gt;Who is shocked to find out that the habits that led to RSI the first time, led to it a second time?
After the pain began to come back, I got a &lt;a href="https://shop.keyboard.io/collections/the-keyboardio-model-100/products/model-100"&gt;Keyboardio Model 100&lt;/a&gt; and it has largely resolved my pain.
It is a great thing to have this pain resolved.
I've had to make some custom items to use them portably (notably, a custom lapdesk for my keyboard and laptop riser).&lt;/p&gt;
&lt;p&gt;Some other issues are also being taken care of, too, and my energy levels are up.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Played a lot of chess, and got involved in my local club again.&lt;/strong&gt;
I took a break from attending my local chess club for a while, because life got in the way and then I was still figuring out my gender identity and starting transition.
It was nerve wracking going back, when I knew people would recognize me.
I was nervous about their reactions, fearful of deadnaming or incorrect pronouns.
But it went great, and I went back not only as an out trans woman, but as a volunteer who helps run the club and is organizing our first official rated tournament!&lt;/p&gt;
&lt;p&gt;A big part of why I went back and started volunteering is because FIDE, the international governing body for competitive chess, made some bad regulations that impede trans women from competing in women's chess events.
If they want to keep us, keep &lt;em&gt;me&lt;/em&gt;, out of chess?
Well then I'm going to come back and be very visible in my local club, and run tournaments.
You can't keep us out.
We're here, and we'll always be here.&lt;/p&gt;
&lt;p&gt;I've played quite a bit of chess this year and fell into bullet chess.
I also stopped studying, so hit a bit of a rating slump for a while.
This is okay, and it's been enjoyable, but it's probably time for that to change.&lt;/p&gt;
&lt;p&gt;Now I'm a certified club tournament director with the USCF (this is unimpressive: it just means I read the rules and filled out a form).
I can run rated tournaments!
To upgrade to being a local tournament director, the next step up to run or assist with larger tournaments, I need to run a few small ones and I need to play in more tournaments.
I'll get there.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Parenting is great, parenting is a challenge. I'm a good mom.&lt;/strong&gt;
I've come into that feeling this year, finally letting go of a lot of the self-doubt.
Not all of it, mind, but enough that I can confidently conclude that I &lt;em&gt;am&lt;/em&gt; a good parent.
We have plenty of challenges with the kids, and we get through them.&lt;/p&gt;
&lt;p&gt;Our kids are 2 and 4, and they're little bundles of energy.
They're showing how uniquely different they each are, and through their eyes we get to see a lot of good things in the world.
I've started to hit a rhythm with the 4 year-old of coworking in my office sometimes, and it's been a great way to bond.&lt;/p&gt;
&lt;h2 id="community-and-world"&gt;Community and world&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Another war...&lt;/strong&gt;
This time, war in Gaza.
There's a lot that can be said.
There's little that I will say here, now, for this is such a charged topic right now that I don't have words for.
Here's &lt;a href="https://blog.paulbiggar.com/i-cant-sleep/"&gt;a post by Paul Biggar&lt;/a&gt; that says it better than I can.&lt;/p&gt;
&lt;p&gt;But the one thing I will say is: My heart breaks at every life lost.
Every single person who is being killed as part of a genocide, being forced out of their homes: I cry for them.
My heart breaks for them.&lt;/p&gt;
&lt;p&gt;Why do we keep killing people?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A few of my friends had/have major health struggles.&lt;/strong&gt;
I won't go into more detail, but it defined parts of the year.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Found community in a Staff+ Engineer Roundtable.&lt;/strong&gt;
I ran a roundtable meeting for staff+ engineers (or anyone interested!) for a while at Recurse Center.
Through this meetup, I found community with some staff+ engineers and made some deep connections.
It wasn't a forever sort of thing to run, and I'm glad I ran it and that it ran its course.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Started attending a Quaker meeting.&lt;/strong&gt;
I've long felt a draw to some sort of organized coming together as a way of exploring and acting on my values.
For some time, I attended a Unitarian Univeralist church.
Now we've begun attending an unprogrammed Quaker meeting in our town, and it is really nice.
We've met a lot of lovely people.
I've been accepted in full.
And it's nice being in community with people who share my values.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I re-entered social media, via Mastodon!&lt;/strong&gt;
I'm now on Mastodon, and you can find me &lt;a href="https://tietz.social/@nicole"&gt;@nicole@tietz.social&lt;/a&gt;.
It's been fun so far!
New followers are welcome.
I'm going to follow few people, probably, to keep my feed nice and tidy.
One of my favorite things is being able to disable boosts in my feed, which reduces the stimulation and makes it more usable for me.&lt;/p&gt;
&lt;h1 id="last-year-s-goals"&gt;Last year's goals&lt;/h1&gt;
&lt;p&gt;Okay, whew, that was a lot this year.
What about what I wanted to do, though?&lt;/p&gt;
&lt;p&gt;My &lt;a href="https://ntietz.com/blog/2022-reflections-2023-goals/"&gt;post last year&lt;/a&gt; had some of my goals and anti-goals for this year.
How did I do on those?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ I wanted to keep writing, and I did more than what I set out to do! This one was a success and really reminded me how much I &lt;em&gt;love&lt;/em&gt; writing.&lt;/li&gt;
&lt;li&gt;✅ I put one side project into production, sort of, then took it back out of production. I consider this a victory and since I'm tabulating it myself, no need to be a pedant. I do want to keep not-productionizing things, because it is such a stressor for me. I don't want my hobbies to be a second job.&lt;/li&gt;
&lt;li&gt;✅ I did avoid learning about DevOps-y tooling in my free time, unless you count learning some Fly stuff. This one kind of comes for free if I never try to do deployments of any of my stuff on my own time!&lt;/li&gt;
&lt;li&gt;✅ I mostly stayed active in RC, though I had some lulls. It is a reasonably sized community which can be overwhelming at times. I've had to cut back on how many things I participate in, to balance with deeper projects and more full participation in the places I do stay engaged.&lt;/li&gt;
&lt;li&gt;❌ I did &lt;em&gt;not&lt;/em&gt; establish learning habits this year, and was very ad hoc with it. So this one is a miss! But I also am glad I didn't, because the approach for the year was great.&lt;/li&gt;
&lt;li&gt;✅ I did keep in touch with people at RC! I kept in contact with some current friends and made a few new ones.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Overall, I think I did really well on those goals.
They were broad and about what I generally was interested in doing and &lt;em&gt;not&lt;/em&gt; doing, and that's a good pattern for me.
Broad strokes, and useful for directionally deciding on what to do, rather than any specific deliverables.
Setting anti-goals was more helpful for me than setting normal goals, so I'll do that again.&lt;/p&gt;
&lt;h1 id="hopes-and-goals-for-2024"&gt;Hopes and goals for 2024&lt;/h1&gt;
&lt;p&gt;I don't do predictions or resolutions, but reflecting on what I'd &lt;em&gt;like&lt;/em&gt; next year to look like is helpful.
It puts me in the right mindset to do my best to make the reality I want to see.
Here's what I'd like to do in 2024.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep my rights.&lt;/strong&gt;
This is the headliner, because trans people are Republicans' favorite punching bags right now.
This is an election year in the US, and attacks on trans rights rage across the country.
I could be arrested for using the bathroom in Florida.
"Drag" bans proliferate in ways that can make being trans in public illegal.
So my goal, with the election, is to emerge from 2024 still having my rights: &lt;em&gt;my right to exist, my right to be a parent, my right to my medical treatment&lt;/em&gt;.
I'm prepared to do whatever I need to to save myself and others, and I hope I'm able to safely remain in the home I love.
I'm afraid, I'm so deeply afraid of what 2024 can bring if Republicans win.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No personal-time side projects into production.&lt;/strong&gt;
This one will probably be a forever anti-goal for me.
I just don't &lt;em&gt;enjoy&lt;/em&gt; doing ops-y stuff but I feel its siren song; that yak has a lot to shave.
It's important to preserve my free time by &lt;em&gt;not&lt;/em&gt; making production web apps since the maintenance is high.
I've dropped the "don't learn more (dev)ops (at home)" anti-goal because I won't run into it if I'm not deploying things.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Strike a better balance with calls and making.&lt;/strong&gt;
Since finding my extrovert energy this year, I started to overschedule myself.
I met a lot of people and felt a need to have coffee chats with all of them.
But... I started to realize I was overscheduled, leaving little time for longer chats with my &lt;em&gt;closer&lt;/em&gt; friends.
It also cut into time I could have used for making things!
So next year I want to have more chats with close friends and fewer with new friends.
I want to reclaim some of that time to make physical things, probably more picture frames and some jewelry.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Continue writing, and expand my writing.&lt;/strong&gt;
This blog is one of my main creative outlets, and I'm going to continue it.
I like the weekly schedule, and I will keep that up.
At the same time, I have some other writing I'd like to do.
I have a few unedited personal pieces, mostly about gender, and I have some sci-fi ideas dying for me to try to write them.
So I want to actually take a swing at that.&lt;/p&gt;
&lt;p&gt;Where do I put those?
They may wind up on another section on this site, or they may just be shared with friends.
Suggestions are welcome.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do some comedy!&lt;/strong&gt;
Making people laugh has been fun for all my life, but I never thought I could "do" comedy.
It turns out that being trans, there are a &lt;em&gt;lot&lt;/em&gt; of things that are really funny.
I've started writing a stand-up routine around that sort of thing, and I want to try that out this year.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stay active in my communities.&lt;/strong&gt;
RC is one of my favorite communities and I'm going to keep being active in the RC community and stay in touch with people.
I'm also going to keep meeting our neighbors in our town and hanging out with them or sharing food.
It's nice having a local community, too.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep being a good parent and partner.&lt;/strong&gt;
There's not a lot to say here except that it's a lot of work and it's a big part of my life and identity, so it has to be here.
It would be incomplete to list all my other things I'm doing and not mention my family.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Finish voice training.&lt;/strong&gt;
Voice training is a challenging part of transition for those that choose to do it.
I have always been deeply uncomfortable with my voice.
This year I've found a voice that is so authentically me and that I &lt;em&gt;like&lt;/em&gt; to hear, that is not dysphoric to hear.
The work remaining is generalization, the ability to use it in all life situations.
There are other fine-tuning things that could be done, but this is the main one, and I want to complete this.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Improve my ergonomic setup and my accessible options.&lt;/strong&gt;
I have an okay ergonomic setup today.
My custom lapdesk allows me to travel around the house with my laptop, but it doesn't allow me to go to the coffee shop, and work travel coming up has brought this pain into focus.
Suddenly, I need the ability to travel to a hotel with my ergonomic keyboard.
So this year, I'd like to improve things in two ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Build a more compact travel keyboard setup. This one I planned out while on a run last week and now I need to prototype it&lt;sup class="footnote-reference" id="fr-prototypes-in-production-1"&gt;&lt;a href="https://ntietz.com/blog/reflecting-on-2023-preparing-for-2024/#fn-prototypes-in-production"&gt;[3]&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
&lt;li&gt;Learn to use Talon again and integrate it into my daily work. This is something that's important so that I am not so fully reliant on keyboards. It would allow me to go more places &lt;em&gt;without&lt;/em&gt; a keyboard while retaining an input mechanism.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Do more technical projects.&lt;/strong&gt;
This year I made a programming language, and I hit a groove of working on medium-term technical projects.
I want to do more of these in 2024!
On the docket are cryptography, databases, and designing another programming language (implementation may take longer).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Go back into competitive chess.&lt;/strong&gt;
I've been playing chess casually this year.
Next year I want to lean back into it in a more competitive way.
I'm running a rated tournament in January!
I'd like to also like to play in a local tournament.
Another thing I should do is build my black opening repertoire more explicitly&lt;sup class="footnote-reference" id="fr-premoves-only-1"&gt;&lt;a href="https://ntietz.com/blog/reflecting-on-2023-preparing-for-2024/#fn-premoves-only"&gt;[4]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep my mental health strong.&lt;/strong&gt;
Next year is going to be a challenging year, and I will put deliberate focus on keeping my mental health where it is (or better).
Not a lot else to say, I think.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;So, that's it!
I've put a lot into this post, and if you've made it this far: thank you.
A big part of what I do is learn in the open, and writing is thinking.
Writing this sort of reflection helps me.&lt;/p&gt;
&lt;p&gt;2023 had a lot in it, with some bad things and lots of very good things.
I'm hoping 2024 shifts the balance to more good and less bad, but I'm prepared for it either way and will practice self-care to get through it whole and healthy.&lt;/p&gt;
&lt;hr /&gt;
&lt;hr /&gt;&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-repeated-from-last-year"&gt;
&lt;p&gt;This introduction is lightly edited from last year. There's not a whole lot else to say to introduce it! &lt;a href="https://ntietz.com/blog/reflecting-on-2023-preparing-for-2024/#fr-repeated-from-last-year-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-not-my-writing"&gt;
&lt;p&gt;I have not, and never intend to, let LLMs be involved with my writing. At the very least, not for something like this blog. It's so deeply personal. &lt;a href="https://ntietz.com/blog/reflecting-on-2023-preparing-for-2024/#fr-not-my-writing-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-prototypes-in-production"&gt;
&lt;p&gt;Of course, the last "prototype" lapdesk is still in constant use by me, with bare wood. So this prototype will likely end up in daily use, too. But that's fine! Only through using it can I figure out what I want to do differently next time. &lt;a href="https://ntietz.com/blog/reflecting-on-2023-preparing-for-2024/#fr-prototypes-in-production-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-premoves-only"&gt;
&lt;p&gt;My bullet/blitz black repertoire is "premove 1. ... e5", which works surprisingly well. You have the shock factor of playing some gambit lines as a premove.
I want to see how far I can extend this, and I also want to just plain learn the acceptable lines here. &lt;a href="https://ntietz.com/blog/reflecting-on-2023-preparing-for-2024/#fr-premoves-only-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Fri, 29 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/reflecting-on-2023-preparing-for-2024/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Backing up mastodon</title><link>https://ellie.wtf/notes/backing-up-mastodon/</link><description>&lt;p&gt;I'm the administrator of https://bikers.social, a mastodon instance for bikers 🏍️&lt;/p&gt;
&lt;p&gt;Postgres is the most important part of an instance backup. If you lose your db, it's game over. My secrets are also backed up.&lt;/p&gt;
&lt;p&gt;My old mastodon backup policy consisted of regular &lt;code&gt;pg_dumps&lt;/code&gt;, and &lt;code&gt;scp&lt;/code&gt; to another machine. This was totally good enough, as that machine was &lt;em&gt;also&lt;/em&gt; backed up to my local NAS. I did have to semi-regularly manually clean up old backups though, and now that the instance has been going a while it would be great to have something better.&lt;/p&gt;
&lt;p&gt;I've recently revamped it a bit to use Cloudflare R2.&lt;/p&gt;
&lt;h2 id="r2"&gt;R2&lt;/h2&gt;
&lt;p&gt;I use R2 for all of the instance's assets. It's much cheaper than S3 for storage, and also has no egress fees! I'd be a bit too concerned about surprise bandwidth bills with S3.&lt;/p&gt;
&lt;p&gt;As of May 2023, R2 also supports &lt;a href="https://developers.cloudflare.com/r2/buckets/object-lifecycles/" rel="external"&gt;lifecycle policies&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="setup"&gt;Setup&lt;/h2&gt;
&lt;p&gt;First up, make a new R2 bucket. This will be used for storage. Ensure it is not publicly accessible!&lt;/p&gt;
&lt;p&gt;Then we want to setup the local aws cli. I created an API token scoped to the bucket, with object read/write, and limited only to the IP address of my mastodon server. Run &lt;code&gt;aws configure&lt;/code&gt;, and paste in your new account ID and secret.&lt;/p&gt;
&lt;blockquote class="markdown-alert-note"&gt;
&lt;p&gt;The AWS cli will, by default, assume that the endpoint you wish to use is Amazon. Newer versions of the CLI can be configured in config, but chances are your package repository is out of date. For all subsequent commands, I set&lt;/p&gt;
&lt;p&gt;&lt;code&gt;alias aws='aws --endpoint-url https://&amp;lt;account ID&amp;gt;.r2.cloudflarestorage.com'&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We can then setup the lifecycle policy.&lt;/p&gt;
&lt;p&gt;Go to your bucket settings, then lifecycle policy&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://img.ellie.wtf/i/ada76f57ae8dc3a95b7f3b9f3ef966b1e712e3f530b4f4820088ff1ba82574c5.png" /&gt;&lt;/p&gt;
&lt;p&gt;I setup a lifecycle rule to keep daily backups for a week, weekly backups for 6 weeks, and monthly backups for 6 months.&lt;/p&gt;
&lt;h2 id="script"&gt;Script&lt;/h2&gt;
&lt;p&gt;My backup script is super simple. When run, it creates a file for the current day, and then uploads it to r2 with the specified prefix.&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #928374; font-style: italic;"&gt;#! /bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #83A598;"&gt;prefix&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;=&lt;/span&gt;&lt;span style="color: #83A598;"&gt;${1&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;:-&lt;/span&gt;&lt;span style="color: #83A598;"&gt;daily}&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #83A598;"&gt;date&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;=&lt;/span&gt;&lt;span style="color: #A89984;"&gt;$(&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;date&lt;/span&gt;&lt;span style="color: #A89984;"&gt; '&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;+%Y-%m-%d&lt;/span&gt;&lt;span style="color: #A89984;"&gt;')&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;pg_dump&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; -Fc -Z 0 -U&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; mastodon mastodon_production&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; |&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt; xz&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; -T4&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; &amp;gt;&lt;/span&gt;&lt;span style="color: #83A598;"&gt; $date&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;.sql.xz&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;aws&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; --endpoint-url&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; https://&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;account i&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;.r2.cloudflarestorage.com s3 cp ./&lt;/span&gt;&lt;span style="color: #83A598;"&gt;$date&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;.sql.xz s3://&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;bucket nam&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;/&lt;/span&gt;&lt;span style="color: #83A598;"&gt;$prefix&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;/&lt;/span&gt;&lt;span style="color: #83A598;"&gt;$date&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;.sql.xz&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If no prefix is specified, we assume daily backups.&lt;/p&gt;
&lt;p&gt;I've chosen &lt;code&gt;xz&lt;/code&gt; for my backups. It produces some of the best compression, at cost of being very slow. My mastodon instance is pretty overkill in terms of spec, due to it being a hetzner box and my instance not being too high traffic.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pg_dump -Fc -Z 0&lt;/code&gt; - using the custom format, but disabling compression as we're doing that with xz&lt;/p&gt;
&lt;p&gt;&lt;code&gt;xz -T4&lt;/code&gt; will use xz compression, and no more than 4 threads. I tried &lt;code&gt;-T0&lt;/code&gt; and it absolutely pegged all 16 threads, which was a bit excessive. 4 still completes fast enough.&lt;/p&gt;
&lt;h2 id="crontab"&gt;Crontab&lt;/h2&gt;
&lt;p&gt;After testing it manually with&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;./backup.sh daily&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;./backup.sh weekly&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;./backup.sh monthly&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and ensuring all files were created OK, I setup my crontab&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;0 12 * * * /home/mastodon/backup.sh daily&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;0 13 * * 0 /home/mastodon/backup.sh weekly&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;0 14 1 * * /home/mastodon/backup.sh monthly&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There's some duplicate work, but that's not a huge concern here. I've scheduled them during waking hours for me, as I also set it up to ping me on Telegram when the backup completes.&lt;/p&gt;
&lt;h2 id="improvements"&gt;Improvements&lt;/h2&gt;
&lt;p&gt;In the future, I may change this to use &lt;a href="https://github.com/wal-g/wal-g" rel="external"&gt;wal-g&lt;/a&gt;. I think this would be pretty overkill for what we have now, and the backup/restore process with &lt;code&gt;pg_dump&lt;/code&gt; is super simple. If the database size gets to be too large, or if it makes sense to have point-in-time recovery, I'll revisit this.&lt;/p&gt;</description><author>Ellie's Notes</author><pubDate>Fri, 29 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ellie.wtf/notes/backing-up-mastodon/</guid></item><item><title>2023 in Retrospective</title><link>https://blog.bayindirh.io/blog/2023-in-retrospective/</link><description>&lt;p&gt;The year is ending in a couple of days, and I wanted to leave a public note about how it went for me.&lt;/p&gt;
&lt;p&gt;2023 was an interesting year. I spent a lot of time getting used to my new life, and to be honest, it went well. I’m happier than ever, and learning to handle my life in this new chapter.&lt;/p&gt;
&lt;p&gt;The biggest change was how I allocate my time during the day. This nudges me to learn to downscale and manage my time better or be more efficient in how I accomplish things in general. This part of my transition is not over yet, I’m actively downscaling in both physical and digital ways. For example, I don’t have a personal desktop computer anymore, but living with a single laptop. I have almost completed my move to cloud storage, converging and simplifying it in the process. I’m trying to reduce the number of my physical belongings, however that part is going slower than I dreamt of. 2023 brought minimalism with all its peculiarities, but it’s not a failure by any means.&lt;/p&gt;
&lt;p&gt;Another small yet important change was finishing transitioning to fountain pens for note-taking. I have disposable pens, but new ones are not being added much. In the end, I’ll need to have a couple because I use rulers sometimes, and there are special-purpose ones, like pigment markers, that I’ll need to stock, but all the grunt work is handled by fountain pens, which bring joy and do not create waste except the glass bottles you empty, which can be recycled. I can say that I’m more mindful of the amount of plastic I consume, and I try to actively recycle it (whether it’s done or not is another issue). I’ll be talking about note-taking and fountain pens in the soonish future.&lt;/p&gt;
&lt;p&gt;I continued learning Go. I wanted to be able to have something between Python and C++ and Go filled that gap perfectly. Simpler than C++, compiled, garbage collected, and fast enough. It’s open source and has a GCC implementation, which is crucial for me. All in all, I love the language and its ergonomics. It proved itself to be a great companion for my automation and downscaling efforts. I may write what I think about Go in a future blog post.&lt;/p&gt;
&lt;p&gt;I continued implementing and almost finished one of the tools I wanted to have with my newly acquired Go knowledge: Nudge, a small command line tool to interface with Pushover for sending push notifications to devices from the Linux command line, which is a crucial requirement for “lights-out operations” which I want to move towards for the mundane things I routinely do in front of a computer because I want to have more time for myself and people I care.&lt;/p&gt;
&lt;p&gt;Lastly, with the AI revolution, I became way more mindful of how these systems are trained, and its effects on our privacy and free software. As a result, changed my trajectory towards services and systems which do not devour your things for their profit without your consent. During that transition, I also left Google search for Kagi, which is something you pay for to use. However, this payment makes sure that you don’t become the product during the process, at least for now. I’m pretty happy about this decision, and I recommend you to give Kagi a try.&lt;/p&gt;
&lt;p&gt;2024 will bring the next step in many of these endeavors and will add new ones on top of it. I’m hopeful that the new year will bring better things upon us since 2023 was enough both for me and our planet as a whole.&lt;/p&gt;
&lt;p&gt;Until next time,&lt;/p&gt;
&lt;p&gt;Be kind.&lt;/p&gt;</description><author>bayindirh</author><pubDate>Fri, 29 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.bayindirh.io/blog/2023-in-retrospective/</guid></item><item><title>Downscale images in url2epub</title><link>https://b.yuxuan.org/url2epub-downscale-images</link><description>&lt;p&gt;Since &lt;a href="https://b.yuxuan.org/url2epub-dropbox"&gt;I added support to my new Kobo eink reader to url2epub&lt;/a&gt;, I&amp;rsquo;ve been reading a lot of long web articles on it, and one thing starts to bother me: Kobo is the only epub reader, hardware or software, that do not auto downscale bigger images in my knowledge.&lt;/p&gt;

&lt;p&gt;For example, when I read &lt;a href="https://www.washingtonpost.com/lifestyle/interactive/2023/tokitae-lolita-orca/"&gt;a Washington Post article about a captive orca&lt;/a&gt;, this is quite annoying:&lt;/p&gt;

&lt;figure&gt;
  &lt;img alt="Article rendered on Kobo Sage from url2epub with original size of image being cropped" src="https://lh3.googleusercontent.com/pw/ABLVV864M2JKxburwhZVTO8Wy6kx0PDUulHRjjOW8pbyFpg8vtRB8cjzk2wsC0i3G4s2ciSp6uOtjjZ9Al8f_iFBvKwuIJiKDU7A4yOUlynBG83bpnTKRqd25rlvesFfTYqHcpG8NkXK0AHo6QBDq1sKhWR0zw=w720" /&gt;
  &lt;figcaption&gt;&lt;a href="https://photos.app.goo.gl/s5Wwv4UoXtMLFSCf9"&gt;Article rendered on Kobo Sage from url2epub with original size of image being cropped&lt;/a&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;So I started to fix it, by implementing a downscaling feature when grayscaling the images to be packed in to epub files.&lt;/p&gt;

&lt;p&gt;It turns out that scaling an image is not a simple thing, as there are a lot of different ways to decide &amp;ldquo;how&amp;rdquo; (&lt;a href="https://www.imagemagick.org/Usage/filter/"&gt;Image Magick project has a page describe in details of different filters&lt;/a&gt;). The general consensus is that for downscaling &lt;a href="https://www.imagemagick.org/Usage/filter/#lanczos"&gt;Lanczos&lt;/a&gt; is usually the best, but it&amp;rsquo;s also complicated to implement.&lt;/p&gt;

&lt;p&gt;Since here in reality I&amp;rsquo;m only dealing with grayscaled images, that every color is only represented by a single number (a 16-bit unsigned int for the grayness), I decided to do something much simpler: Just calculated a weighted average of the color of the original pixels of the new pixel is representing.&lt;/p&gt;

&lt;p&gt;For example, when you downscale an originally 720x540 image to 480x405, you can map the new &lt;code&gt;(0, 0)&lt;/code&gt; pixel to a range of 0-1.5 for both x and y,  so it&amp;rsquo;s the combination of the original &lt;code&gt;(0, 0)&lt;/code&gt; (full), &lt;code&gt;(0, 1)&lt;/code&gt; (half), &lt;code&gt;(1, 0)&lt;/code&gt; (half), and &lt;code&gt;(1, 1)&lt;/code&gt; (&lt;sup&gt;1&lt;/sup&gt;&amp;frasl;&lt;sub&gt;4&lt;/sub&gt;). So just calculated the weighted average of the 4 colors from the original image to get the new color for the &lt;code&gt;(0, 0)&lt;/code&gt; pixel on the new image.&lt;/p&gt;

&lt;p&gt;With that implemented, I also need to decide how the API should work for downscaling. I could pass in both desired width and height to do the downscaling, but considering that Kobo eink readers can be used in both vertical or horizontal orientation, if you just downscale for the width, it could still be too high when you switch to horizontal and now the width limitation becomes the height limitation. For that reason, and also for the reason of for the sake of simplicity, I chose to only set one number being the limitation of both width and height. In &lt;a href="https://t.me/url2rM_bot"&gt;the Telegram bot&lt;/a&gt;, this is the new &lt;code&gt;/fit N&lt;/code&gt; command, and in &lt;a href="https://github.com/fishy/url2epub/blob/main/REST.md"&gt;the REST API&lt;/a&gt; this can be set via the new &lt;code&gt;fit=N&lt;/code&gt; url query.&lt;/p&gt;

&lt;p&gt;Then the next problem is to decide what&amp;rsquo;s the number I need to set for my own Kobo Sage. &lt;a href="https://us.kobobooks.com/products/kobo-sage#:~:text=by%20library%20location.-,Technical%20Specs,-Display"&gt;The official tech spec&lt;/a&gt; says the screen resolution is 1440x1920, I tried both 1280 and 1024 and it seems 1280 works best:&lt;/p&gt;

&lt;figure&gt;
  &lt;img alt="Article rendered on Kobo Sage from url2epub with image downscaled to 1280x1280" src="https://lh3.googleusercontent.com/pw/ABLVV85JqIeu8-GW9-ETXV_k--YwByQFEegyCNFFTPg2SoRmniAgWdhJVPOYr7yBeEt54Yp8maAKTdA6EtFHaGGX4RpERiZyxHUwhKFw4vVQDQbQy3FJfbMQpn_eHZT3bNmuBno4bDdAkitKuIQwWmhHeTkOog=w720" /&gt;
  &lt;figcaption&gt;&lt;a href="https://photos.app.goo.gl/KJWKo1XFCkKkxVm87"&gt;Article rendered on Kobo Sage from url2epub with image downscaled to 1280x1280&lt;/a&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img alt="Article rendered on Kobo Sage from url2epub with image downscaled to 1024x1024" src="https://lh3.googleusercontent.com/pw/ABLVV87kg5G8XGyVTQ8btjalMLFVZZ6TiqQ-rFP7iylEKRKqwwGzUePLWkZALwQ7zXAd-NFnUhwBLi_RoxrcezQ92rT1bmfcTzRMxFXShUACnY5J_CCP7KXkTiZcyxmEXH02JRCO4VLLVnKnnL2jGccMGjcl8A=w720" /&gt;
  &lt;figcaption&gt;&lt;a href="https://photos.app.goo.gl/gnexEsvro8bFRoTr8"&gt;Article rendered on Kobo Sage from url2epub with image downscaled to 1024x1024&lt;/a&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;And looks like my downscaling implementation is rendered pretty sharp on Kobo&amp;rsquo;s eink display!&lt;/p&gt;</description><author>La Vita è Bear</author><pubDate>Fri, 29 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://b.yuxuan.org/url2epub-downscale-images</guid></item><item><title>Running OpenStack Ironic Unit Tests in PyCharm</title><link>https://jay.jvf.cc/posts/openstack-unit-test-pycharm/</link><description>&lt;h1 id="openstack-ironic--pycharm--unit-tests"&gt;OpenStack Ironic + PyCharm + Unit Tests&lt;/h1&gt;
&lt;p&gt;I assumed for a long time it was likely trivial to get OpenStack Ironic unit
tests running in PyCharm, but never took the time to get a reliably working
configuration &amp;ndash; until today. I&amp;rsquo;m going to describe the configuration here so
hopefully it can be replicated by others.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s my belief this should work for most OpenStack projects using oslotest, but
I&amp;rsquo;ve only used this method so far with Ironic-based projects. As always, please
check the codebase and documentation if anything doesn&amp;rsquo;t work; this is a
point-in-time blogpost with limited scope not a full document.&lt;/p&gt;</description><author/><pubDate>Thu, 28 Dec 2023 23:33:52 GMT</pubDate><guid isPermaLink="true">https://jay.jvf.cc/posts/openstack-unit-test-pycharm/</guid></item><item><title>Help a Family Create a Memory</title><link>https://olshansky.info/posts/help-a-family-create-a-memory/</link><description>&lt;p&gt;&lt;em&gt;Snap! Smile! Turn around! Look here!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;On a sunny day, with a breath of fresh air,
I took a short walk, on an off-day winter holiday,
Witnessing families, both large and small, capturing memories on a casual stroll.&lt;/p&gt;
&lt;p&gt;These snapshots may sync to the cloud, or begin to traverse,
Soaring about, sometimes emerging to the &lt;em&gt;Meta&lt;/em&gt;verse,
But without a digital frame, they often linger, forgotten, out and about.&lt;/p&gt;
&lt;p&gt;If the photo stands out, it&amp;rsquo;ll likely be aired,
But if not all are in frame, it may fade out, uncared.&lt;/p&gt;</description><author>🦉 olshansky 🦁</author><pubDate>Thu, 28 Dec 2023 20:41:44 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/posts/help-a-family-create-a-memory/</guid></item><item><title>The time has (not yet) come</title><link>https://deadlime.hu/en/2023/12/28/the-time-has-not-yet-come/</link><description>Creating a desk clock with Raspberry Pi Pico</description><author>deadlime</author><pubDate>Thu, 28 Dec 2023 12:29:46 GMT</pubDate><guid isPermaLink="true">https://deadlime.hu/en/2023/12/28/the-time-has-not-yet-come/</guid></item><item><title>Summary: Who not How by Dan Sullivan and Dr. Benjamin Hardy</title><link>https://www.chestergrant.com/summary-who-not-how-by-dan-sullivan-and-dr-benjamin-hardy</link><description>&lt;div class="posthaven-post-body"&gt;&lt;p&gt;&lt;/p&gt;&lt;center&gt;        &lt;div class="posthaven-gallery" id="posthaven_gallery[2091983]"&gt;
                  &lt;p class="posthaven-file posthaven-file-image posthaven-file-state-processed"&gt;
          &lt;img class="posthaven-gallery-image" src="https://phaven-prod.s3.amazonaws.com/files/image_part/asset/3124603/BiXp8-uMaX0ch2NtnYhYKXlXom0/medium_cover.png" /&gt;
        &lt;/p&gt;

        &lt;/div&gt;
&lt;/center&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;1. Results, not effort, is the name of the game. You are rewarded in life by the results you produce, not the effort and time you put in.&lt;/p&gt;&lt;p&gt;2. It can be easy to focus on How, especially for high achievers who want to control what they can control, which is themselves. It takes vulnerability and trust to expand your efforts and build a winning team. It takes wisdom to recognize that 1) other people are more than capable enough to handle much of the Hows, and 2) that your efforts and contribution (your “Hows”) should be focused exclusively where your greatest passion and impact are. Your attention and energy should not be spread thin, but purposefully directed where you can experience extreme flow and creativity.&lt;/p&gt;&lt;p&gt;&lt;b&gt;3. If you’re ready to realize a much bigger and more powerful future, then you must stop asking yourself, “How can I accomplish this?” That question, although common, leads to mediocre results, frustration, and a life of regrets. A much better question is: “Who can help me achieve this?”&lt;/b&gt;&lt;/p&gt;&lt;p&gt;4. That’s what real leadership is: Creating and clarifying the vision (the “what”), and giving that vision greater context and importance (the “why”) for all Whos involved. Once the “what” and “why” have clearly been established, the specified “Who” or “Whos” have all they need to go about executing the “How.” All the leader needs to do at that point is support and encourage the Who(s) through the process.&lt;/p&gt;&lt;p&gt;&lt;b&gt;5. You don’t get preferential treatment with Hows, but with Whos who know what they’re doing.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;6. It is critically important to understand that Who Not How goes both ways. Yes, Tucker, Reid, and I are Dan’s Whos on this book. But Dan is also a Who to each of us.&lt;/p&gt;&lt;p&gt;7. Ultimately, Who Not How is about teaching you how to focus on what you can do, and then finding other Whos to do what they can do.&lt;/p&gt;&lt;p&gt;8. In every “Who” relationship, you will have Whos, and you will also be a Who. No Who is viewed as better or more important than the other. All Whos are essential to getting the project done. There is love and respect among Whos. Each member of the team views the other as a collaborator on a shared mission, and each member wants to be a hero to the others.&lt;/p&gt;&lt;p&gt;9. Not only must the Who fully own the How, but they must have complete permission to do so.&lt;/p&gt;&lt;p&gt;&lt;b&gt;10. If you’re going to apply higher levels of teamwork in your life, you’ll need to relinquish control over how things get done.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;11. A core aspect of leadership is being explicit about the vision. The more explicit you are in what you want, the faster you’ll attract the right Whos to help you achieve that vision. The leader explains the “What” and “Why” and then allows the “Who” to execute the “How.”&lt;/p&gt;&lt;p&gt;&lt;b&gt;12. “How” is linear and slow. “Who” is non-linear, instantaneous, and exponential.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;13. TIME - Today Is My Everything.&lt;/p&gt;&lt;p&gt;14. Your Whos manage themselves; they aren’t managed by you. They have full responsibility for how they handle themselves because you’ve made the vision abundantly clear and exciting. You’ve then given them full ownership over executing and achieving the vision in whatever way suits them.&lt;/p&gt;&lt;p&gt;15. As it turns out, this isn’t just a fancy idea. Research shows that only 16 percent of creative insight happens while you’re at work. Instead, ideas generally come while you’re at home or in transit, or during recreational activity.&lt;/p&gt;&lt;p&gt;16. You need time and space, and most important, relaxation and recovery, to allow ideas and solutions to ferment and form.&lt;/p&gt;&lt;p&gt;17. The point here is, as you engage in relationships, you expand your efficacy as a person. Your efficacy is your ability to produce results, and it is based on the resources you have to put toward those results. Resources can be financial, but they can also be so much more than that. Encouragement, time, and focus are just as essential as monetary support. Resources not only expand your ability to produce results, but can have a transformational effect on you as a person—on your identity, worldview, and skill level.&lt;/p&gt;&lt;p&gt;18. For example, if you want to improve your health, you could simply get a gym membership. Or you could hire a personal trainer. Yes, this would be an investment, one you may not think you have the capacity to make. However, by hiring a personal trainer, your capabilities and potential in your health and fitness will expand. You’ll be able to produce better results because you’ll get the coaching and support you need. Additionally, by being invested, you’ll be more motivated and focused, not semi-committed.&lt;/p&gt;&lt;p&gt;&lt;b&gt;19. Again, getting Whos is how you get committed.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;20. Meta-analytic research shows that confidence is the by-product of recent performance or recent progress toward your goals. By growing your confidence, your imagination and future will simultaneously grow as well.&lt;/p&gt;&lt;p&gt;21. Procrastination doesn’t only stop your confidence from growing. You also limit your imagination, preventing you from seeking out bigger and bigger goals. Your identity or self-concept becomes limited. You stop believing you can achieve big goals, because your identity is largely shaped by your behavior. And this pattern will cause you to assume the same for your future. Thus, procrastination leads to a small self-image and an increasingly smaller future for yourself. You stop trusting in yourself. You stop believing in yourself.&lt;/p&gt;&lt;p&gt;22. Paradoxically, procrastination is actually a form of wisdom. Procrastination is a psychological phenomenon that occurs when you really want something more for yourself, but you lack the knowledge and capability to do it.&lt;/p&gt;&lt;p&gt;23. Procrastination is a very powerful signal telling you that it’s time to get another Who involved. You’re stuck. You need help.&lt;/p&gt;&lt;p&gt;24. Applying Who Not How, and thus killing procrastination, requires two essential steps: &lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Be radically explicit about your goals. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ask yourself: Who can help me accomplish this goal?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;b&gt;25. Dan has created a tool that helps to define the vision, what successful completion of the project looks like, and why it is so important. He calls this tool the Impact Filter.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;26. The Impact Filter, as a one-page document, solves this most pervasive leadership conundrum, and is comprised of the following questions: &lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;p&gt;What is the project? &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Purpose: What do you want to accomplish? &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Importance: What’s the biggest difference this will make? &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ideal Outcome: What does the completed project look like? &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Best Result: If you do take action? &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Worst Result: If you don’t take action? &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Success Criteria: What has to be true when this project is finished?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;27. Now that you’ve clarified and become explicit about your goal, you must refrain from trying to figure out “How” to accomplish it.&lt;/p&gt;&lt;p&gt;28. You’ll try to convince yourself why other people may not want to get involved. You may think you can’t afford the right Whos, or that you’re not a great leader. All sorts of limiting beliefs will flood through your mind, trying to get you to focus on the Hows, not the Whos. Without question, it takes courage to tell people your vision. It takes courage and leadership to get other people involved.&lt;/p&gt;&lt;p&gt;29. Dan often says, “The purpose of the Impact Filter is first to sell yourself on the vision, because you can’t sell other people unless you’re sold yourself.”&lt;/p&gt;&lt;p&gt;&lt;b&gt;30. There are plenty of incredible and capable people who want to and will help you. All you need to do is tell them your vision. Spell it out for them. The Impact Filter actually does that for you. Then ask yourself: “Who can help me accomplish this goal?”&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;31. Once you’ve identified the needed person to help you accomplish your goal, it’s time to get that person engaged and executing the needed Hows. In order to do so, you’ll need to ensure your vision also matches their vision for themselves, and that you can clearly become a powerful Who to them. If you can, and if helping you achieve your vision will help them simultaneously become who they want to be, then you’ve got your Who.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;32.  Unless you’re brilliant at finding Whos, you should probably find a Who to find your Whos.&lt;/p&gt;&lt;p&gt;33. You can start small. Each small win builds confidence and an increased sense that you can create the life you want. Start by simply eliminating all tasks or distractions that are unnecessary to your future self. Often, we engage in tasks simply out of habit. If it can be eliminated altogether, then eliminate it. Your future self will thank you.&lt;/p&gt;&lt;p&gt;34. Challenge: Add at least one Who to your goals in the next 90 days in whatever area of your life you choose. By adding a Who, your commitment will increase and your behavior will improve.&lt;/p&gt;&lt;p&gt;35. By investing in Whos you not only utilize their time and resources, but also free yourself up to focus your time and attention on your most high-value activities.&lt;/p&gt;&lt;p&gt;36. Some people don’t invest in Whos like Connie because they don’t view them as an investment, but as a cost. They worry about the amount of money they’ll have to pay their Who, rather than thinking about how that Who could elevate their vision and free up their time.&lt;/p&gt;&lt;p&gt;37. The time I spend writing books and recording podcasts is worth at least 10X or even 100X the time I spent scheduling podcasts.&lt;/p&gt;&lt;p&gt;38. Escalation of Commitment: Every time you invest yourself in something, you become more committed to it.&lt;/p&gt;&lt;p&gt;39. Our culture has brainwashed us into avoiding costs rather than making powerful investments in ourselves and our futures.&lt;/p&gt;&lt;p&gt;40. You’re either in the “Time and Effort Economy” or the “Results Economy.”&lt;/p&gt;&lt;p&gt;41. Whos, when selected properly to fit within your vision, are never a cost. Whos are an investment.&lt;/p&gt;&lt;p&gt;42. “You can survive without a community, but you can’t thrive without one.”&lt;/p&gt;&lt;p&gt;43. First and foremost, when it comes to connecting with someone, you should want to be connected with them. It shouldn’t be a chore. There shouldn’t be any desire to avoid contact or escape from them.&lt;/p&gt;&lt;p&gt;44. “I don’t want to work on a relationship. I just want a relationship that works,”&lt;/p&gt;&lt;p&gt;45. The moral to the story: Don’t reach out to someone unless you have something meaningful to offer them. That “something” needs to be real and relevant, not just a compliment or flattery. True and real value.&lt;/p&gt;&lt;p&gt;46. It’s a back-and-forth process. Rather than sitting by yourself, trying to perfect the idea without feedback, it’s far more effective to throw your ideas out there fast, get feedback from your team, and then adjust as you go. The faster you get at throwing out incomplete work, the faster it will transform into something great. Dan calls this the 80 percent rule. You can get to 80 percent of a project very quickly, such as writing a rough draft. However, going from 80 percent to 90 percent is exponentially more work than going from 0 to 80 percent. Going from 90 to 100 percent is a mountain.&lt;/p&gt;&lt;p&gt;47. It all starts by setting a goal, a new and bigger version of your own future. Then your next step is to ask, “Who can help me do this?”&lt;/p&gt;&lt;p&gt;&lt;img /&gt;&lt;/p&gt;&lt;/div&gt;</description><author>Chester Grant</author><pubDate>Thu, 28 Dec 2023 03:38:11 GMT</pubDate><guid isPermaLink="true">https://www.chestergrant.com/summary-who-not-how-by-dan-sullivan-and-dr-benjamin-hardy</guid></item><item><title>Pageripper API: a commercial data-extraction service</title><link>https://zackproser.com/blog/introducing-pageripper-api</link><description>I built an API that extracts data from webpages, even if they're rendered with Javascript. It's defined via Pulumi and deployed on AWS.</description><author>Zachary Proser</author><pubDate>Thu, 28 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/introducing-pageripper-api</guid></item><item><title>A Hobby Coding Biography</title><link>https://www.marginalia.nu/log/97_projects/</link><description>&lt;p&gt;This is a bit of a retrospective of every project I&amp;rsquo;ve worked on, as far as I remember them. I&amp;rsquo;ve tried to unearth any artifacts that remain.&lt;/p&gt;
&lt;p&gt;Far from everything is flattering and resounding success, but then again, maybe that&amp;rsquo;s good. There are definitely patterns in the things that
didn&amp;rsquo;t pan out.&lt;/p&gt;
&lt;h1 id="earliest-traces"&gt;Earliest Traces&lt;/h1&gt;
&lt;p&gt;I was definitely programming stuff, but I don&amp;rsquo;t think it ever amounted to anything tangible. It was more like playing house,
I built GUIs that looked like real applications but didn&amp;rsquo;t really do anything but look cool to me. Real hackertyper vibes.
This was in Java, Delphi and VB script mostly. I also built a bunch of websites on geocities and angelfire. This is definitely
as far back as 1999. I did some QBASIC stuff even earlier, like basic text based games and drawing circles and stuff.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Thu, 28 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/97_projects/</guid></item><item><title>Parsing HTML Fast</title><link>https://shadowfacts.net/2023/parsing-html-fast/</link><description>&lt;p&gt;The urge to overengineer something recently befell me, and the target of that urge was the way Tusker handles HTML parsing. Mastodon provides the content of posts (and profile descriptions, and some other things) as HTML, which means that, in order to display them properly, you need to parse the HTML, a notoriously straightforward and easy task. For a long time, the approach I took was to use &lt;a href="https://github.com/scinfu/SwiftSoup"&gt;SwiftSoup&lt;/a&gt; to parse the HTML and then walk the resulting node tree to build up a big &lt;code&gt;NSAttributedString&lt;/code&gt; that could be displayed.&lt;/p&gt;
&lt;p&gt;And while that technique worked perfectly well, there were a number of downsides that I wasn’t entirely satisfied with. First: SwiftSoup alone is a &lt;em&gt;big&lt;/em&gt; dependency, at around ten thousand lines of code. Since it’s shipping in my app, I take the view that I’m responsible for making sure it doesn’t break—and that’s a lot of surface area to cover (moreover, since it was ported from a Java library, the code is extremely un-Swift-y). Second: it’s not especially fast. Partly because of its aforementioned nature as a Java port (everything is a class, lots of unnecessary copies), but primarily because it’s just doing more than I need it to (the first rule of optimization is do less work): it was parsing a string into a tree and then I was flattening that tree back into a string.&lt;/p&gt;
&lt;p&gt;The &lt;a href="/2022/swift-rust/"&gt;last time&lt;/a&gt; I needed to do something similar, I used lol-html, Cloudflare’s streaming HTML rewriter. But, while that worked (and is still in use for my RSS reader app), it’s written in Rust, and I didn’t want to introduce significant additional complexity to the build process for Tusker. So, writing a new HTML parser in Swift seemed like a great idea.&lt;/p&gt;
&lt;!-- excerpt-end --&gt;
&lt;h2&gt;The Architecture&lt;/h2&gt;
&lt;p&gt;Parsing HTML is no small task, and &lt;a href="https://html.spec.whatwg.org/multipage/parsing.html"&gt;the spec&lt;/a&gt; stands at some 42 thousand words. Fortunately, I get to ignore about half of that. Parsing HTML is divided into two primary stages: tokenizing and tree construction. The tokenization stage takes the character stream as input and produces a stream of tokens (text characters, comments, start/end tags, etc.). The tree construction stages then uses the token stream to build up the actual DOM tree. The main way I’m improving over SwiftSoup in terms of not doing unnecessary work is by skipping tree construction altogether.&lt;/p&gt;
&lt;p&gt;This is based on lol-html’s architecture, which usually uses a simulated tree construction stage (because the tokenizer gets feedback from the tree constructor and may have its state altered based on the current position in the tree) and only switches to the slower, real one when necessary. In my case, though, I don’t even simulate tree construction, since everything I want from the output can be generated directly from the token stream.&lt;/p&gt;
&lt;p&gt;This does mean that in certain circumstances the output of the tokenizer could be incorrect, but that’s not a problem for my use case. For Tusker, I’m only ever going to use it to parse fragments—not whole documents—and the Mastodon backend sanitizes incoming HTML. What’s more, from what I can tell of lol-html’s simulator, most of the cases where (without the tree construction stage) ambiguity arises or feedback is needed aren’t going to come up in my use case. They involve other tag namespaces (SVG, MathML), or things like &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; tags—both of which I’m comfortable with handling incorrectly.&lt;/p&gt;
&lt;h3&gt;Tokenizing&lt;/h3&gt;
&lt;p&gt;The tokenizer is the single biggest component of this project, weighing in at about 1500 lines of code. Fortunately though, the spec is quite clear. It describes in exacting detail the state machine that a spec-compliant tokenizer needs to mimic. To make things simple, all of my code is an almost one-for-one translation of the spec into Swift. There are a few places where I diverged, mostly in the name of performance, but where I didn’t, the code is quite easy to follow.&lt;/p&gt;
&lt;h3&gt;Attributed Strings&lt;/h3&gt;
&lt;p&gt;Converting the token stream to an &lt;code&gt;NSAttributedString&lt;/code&gt; is somewhat more complicated, but not terribly so. The gist of it is that the converter tracks what the style of the string should be at the current point in the token stream. Then, when a text character arrives from the tokenizer, it’s added to a buffer representing characters for which the current styles apply (called a “run”). When an open/close tag token is emitted, the current style is adjusted. After the current styles change, the current run is finished and an attributed string is built for the current text and styles and is appended to the overall string.&lt;/p&gt;
&lt;h2&gt;Optimization&lt;/h2&gt;
&lt;h3&gt;Named Character References&lt;/h3&gt;
&lt;p&gt;The spec requires HTML tokenizers to have some special handling for named character references (things like &lt;code&gt;&amp;amp;amp;&lt;/code&gt;) which aren’t well-formed (e.g., missing the trailing semicolon). Because I was sticking very closely to the spec, this was originally implemented rather inefficiently: when a named character reference began, it would consume further characters one by one while there were any named character references beginning with the accumulated characters. I was just using a regular Swift dictionary to store all of the valid character references, so this was effectively an O(n) operation for each character in the reference.&lt;/p&gt;
&lt;p&gt;That is, to put it mildly, ridiculously inefficient. Instead, the new approach just consumes as many alphanumeric characters as possible until it hits a semicolon. Then, if there’s no valid character reference, it backs up character by character until it finds a valid reference. This optimizes for the common case of a properly-formed reference, but (as far as I can tell) preserves the error-handling behavior that the spec requires.&lt;/p&gt;
&lt;h3&gt;Static Dispatch&lt;/h3&gt;
&lt;p&gt;The way I implemented the tokenizer, there’s a separate method that follows the procedure for each state, each of which returns the next token. The tokenizer itself is also an &lt;code&gt;Iterator&lt;/code&gt; that yields tokens. The &lt;code&gt;Iterator.next&lt;/code&gt; implementation just switches over the current state and dispatches to the correct method.&lt;/p&gt;
&lt;p&gt;It’s fairly common for the procedure for tokenizing to involve switching to a different state. Originally, I did this by changing the &lt;code&gt;state&lt;/code&gt; and then calling &lt;code&gt;next()&lt;/code&gt;. One small optimization I made relatively early was, in instances where the new state is constant, replacing the &lt;code&gt;next()&lt;/code&gt; call with a direct call to the appropriate method for the new state—effectively statically dispatching whenever the state changes.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-prop"&gt;state &lt;/span&gt;= .&lt;span class="hl-prop"&gt;tagName&lt;/span&gt;&lt;span class="hl-kw"&gt;
return &lt;/span&gt;&lt;span class="hl-prop"&gt;next&lt;/span&gt;()&lt;span class="hl-prop"&gt;
&lt;span class="hl-cmt"&gt;// vs.&lt;/span&gt;
state &lt;/span&gt;= .&lt;span class="hl-prop"&gt;tagName&lt;/span&gt;&lt;span class="hl-kw"&gt;
return &lt;/span&gt;&lt;span class="hl-prop"&gt;tokenizeTagName&lt;/span&gt;()
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;UI/NSFont Caching&lt;/h3&gt;
&lt;p&gt;A number of the tags handled by the attributed string converter alter the current font. Originally, I implemented this by looking at the currently-applied styles and using them to assemble a &lt;code&gt;SymbolicTraits&lt;/code&gt; that could be passed to &lt;code&gt;UIFontDescriptor.withSymbolicTraits&lt;/code&gt; on the base font descriptor. When profiling, though, I found a non-trivial amount of time was being spent inside &lt;code&gt;withSymbolicTraits&lt;/code&gt; to look up the actual font that should be used. So, the converter instead caches the known styles and their corresponding font objects.&lt;/p&gt;
&lt;h3&gt;Using &lt;code&gt;Swift.Array&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;One of the first optimizations I’d implemented was a type called &lt;code&gt;InlineArray3&lt;/code&gt; which was a collection type that stored up to three elements inline. Growing beyond that would cause it to switch to a regular Swift array, the storage for which is out of line. The motivation for this was the fact that the vast majority of the HTML tags that the parser sees will have very few attributes, so you might be able to gain a little bit of performance by storing them inline.&lt;/p&gt;
&lt;p&gt;After implementing a simple performance testing harness (discussed later), I went back and tested this, and it turned out that just using &lt;code&gt;Array&lt;/code&gt; was a fair bit faster. Just goes to show that you shouldn’t try to optimize things without having concrete data.&lt;/p&gt;
&lt;h3&gt;Looping in Hot Recursive Functions&lt;/h3&gt;
&lt;p&gt;For a number of states in the tokenizer (such as while tokenizing a tag name), the state machines stays in the same state for multiple iterations before emitting a token. Initially, I implemented this just by having the tokenization function recursively call itself. One small but measurable performance improvement I got came from turning hot recursive paths into loops. So, rather than the function calling itself recursively, the entire function body would be wrapped in an infinite loop and the “recursive call” would simply continue on to the next loop iteration.&lt;/p&gt;
&lt;p&gt;This was one of the more surprising optimizations I made, since I’d expected this to make no difference. Given tail-call optimization, I’d think both approaches would generate basically identical code. And indeed, looking at the disassembly before and after this change seems to confirm that. There are differences, and though they seem very minor, I guess they’re enough to make a difference (though a very, very small one—only about 5ms over 10k iterations).&lt;/p&gt;
&lt;h3&gt;Avoiding Enums with Associated Values&lt;/h3&gt;
&lt;p&gt;Lots of states of the tokenizer involve building up the current token until it’s ready to emit. This means the parser needs to store the in-progress token. My &lt;code&gt;Token&lt;/code&gt; data type is a Swift enum with a handful of different cases. So, the natural way of representing the current token was as an instance variable of an optional &lt;code&gt;Token&lt;/code&gt;. Modifying the current token means, then, pattern matching against a &lt;code&gt;Token&lt;/code&gt; and then constructing the updated &lt;code&gt;Token&lt;/code&gt; and assigning it back.&lt;/p&gt;
&lt;p&gt;Unfortunately, this does not permit in-place modification, meaning that all of the enum’s associated values need to be copied. For primitive types, this might not be a problem. But for more complex types, like arrays and strings, this results in a bunch of extra copies. So, rather than having a single &lt;code&gt;currentToken&lt;/code&gt; property, there are separate properties representing the data for each possible token—each of which can be modified in-place:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;private &lt;/span&gt;&lt;span class="hl-kw"&gt;var &lt;/span&gt;currentToken: &lt;span class="hl-ty"&gt;Token&lt;/span&gt;?&lt;span class="hl-kw"&gt;
&lt;span class="hl-cmt"&gt;// vs.&lt;/span&gt;
private &lt;/span&gt;&lt;span class="hl-kw"&gt;var &lt;/span&gt;currentStartTag: (&lt;span class="hl-ty"&gt;String&lt;/span&gt;, selfClosing: &lt;span class="hl-ty"&gt;Bool&lt;/span&gt;, attributes: [&lt;span class="hl-ty"&gt;Attribute&lt;/span&gt;])?&lt;span class="hl-kw"&gt;
private &lt;/span&gt;&lt;span class="hl-kw"&gt;var &lt;/span&gt;currentEndTag: &lt;span class="hl-ty"&gt;String&lt;/span&gt;?
&lt;span class="hl-cmt"&gt;// etc.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Using &lt;code&gt;Unicode.Scalar&lt;/code&gt; instead of &lt;code&gt;Character&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;My tokenizer originally operated on a stream of Swift &lt;code&gt;Character&lt;/code&gt;s, which represent extended grapheme clusters. The tokenizer, however, only cares about individual Unicode code points—and indeed is specified to operate on code points. So, I changed the tokenizer to operate on the &lt;code&gt;Unicode.Scalar&lt;/code&gt; type. This resulted in a significant speed-up, since the there was no longer any time being spent on the grapheme breaking algorithm.&lt;/p&gt;
&lt;h3&gt;Grouping Character Tokens&lt;/h3&gt;
&lt;p&gt;When emitting the document text, the tokenizer is specified to emit individual characters. As an optimization, though, I introduced an additional token type which emits a whole string of characters in one go. Since long, uninterrupted runs of characters that don’t require further parsing are quite common in actual HTML documents, this eliminates a bunch of the overhead associated with handling a token that comes from switching back and forth between the tokenizer and whatever’s consuming the tokens (that overhead is amortized over the sequence of character tokens).&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;To both test the final performance of the new end-to-end HTML to &lt;code&gt;NSAttributedString&lt;/code&gt; conversion process as well as guide most of the optimizations I described above, I wrote a small benchmarking harness that converts the HTML from the last ten thousand posts on my timeline to attributed strings using the old and new methods. This is basically the exact use case I have for this project, so I’m confident that the benchmarking results are fairly representative.&lt;/p&gt;
&lt;p&gt;All told, the new process is about 2.7× faster when both are built in release mode, and a whopping 8× faster in debug (not terribly important, but nice for me since a debug build is often what’s on my phone).&lt;/p&gt;
&lt;p&gt;I’m very happy with how this project turned out. I greatly enjoyed overengineering/optimizing something fairly self-contained, and the end result meets all of the goals/requirements I had for my use case. If you’re curious to see how it works in more detail, check out &lt;a href="https://git.shadowfacts.net/shadowfacts/HTMLStreamer"&gt;the source code&lt;/a&gt;.&lt;/p&gt;</description><author>Shadowfacts</author><pubDate>Thu, 28 Dec 2023 01:52:42 GMT</pubDate><guid isPermaLink="true">https://shadowfacts.net/2023/parsing-html-fast/</guid></item><item><title>23 from '23</title><link>https://somethingaboutcats.substack.com/p/23-from-23</link><description>The movement you need is on your shoulder</description><author>Something About Cats</author><pubDate>Wed, 27 Dec 2023 22:05:24 GMT</pubDate><guid isPermaLink="true">https://somethingaboutcats.substack.com/p/23-from-23</guid></item><item><title>What I learned during Advent of Code 2023</title><link>https://anisse.astier.eu/aoc-2023-lessons.html</link><description>&lt;p&gt;&lt;a href="https://adventofcode.com"&gt;Advent of Code&lt;/a&gt; is an Advent calendar of small programming puzzles. I participated in &lt;a href="https://adventofcode.com/2023/"&gt;this year's edition&lt;/a&gt;, finishing it for the second time in row. The puzzles of all editions are always accessible.&lt;/p&gt;
&lt;p&gt;The principle is to read the problem, get a puzzle input (more or less tailored to your …&lt;/p&gt;</description><author>Linux Engineer's random thoughts</author><pubDate>Wed, 27 Dec 2023 21:00:00 GMT</pubDate><guid isPermaLink="true">https://anisse.astier.eu/aoc-2023-lessons.html</guid></item><item><title>Hosting web services on my Raspberry Pi</title><link>https://rajkumaar.co.in/posts/services-on-pi/</link><description/><author>Online Home of Rajkumar</author><pubDate>Wed, 27 Dec 2023 05:06:58 GMT</pubDate><guid isPermaLink="true">https://rajkumaar.co.in/posts/services-on-pi/</guid></item><item><title>What Are Kubernetes Operators? (Operators 101: Part 1)</title><link>https://sklar.rocks/what-are-kubernetes-operators/</link><description>&lt;p&gt;If you've used Kubernetes in just about any capacity over the past few years, you've almost certainly leaned on at least one operator to automate a piece of your infrastructure.&lt;/p&gt;
&lt;p&gt;The operator model has been espoused by influencers like Viktor Farcic (aka &lt;a href="https://www.youtube.com/@DevOpsToolkit" rel="external"&gt;@DevOps Toolkit on YouTube&lt;/a&gt;) and &lt;a href="https://twitter.com/kelseyhightower" rel="external"&gt;Kelsey Hightower&lt;/a&gt; for its advanced automation abilities. &lt;a href="https://operatorhub.io" rel="external"&gt;OperatorHub.io&lt;/a&gt; lists over 300 different operators for you to install in your clusters. Kubernetes operators seem to be everywhere!&lt;/p&gt;
&lt;p&gt;But what exactly is an operator? The &lt;a href="https://kubernetes.io/docs/concepts/extend-kubernetes/operator/" rel="external"&gt;official documentation page&lt;/a&gt; about operators is actually fairly short and leaves a lot to the reader's imagination.&lt;/p&gt;
&lt;p&gt;This is the start of a new series, &lt;a href="/tags/operators-101"&gt;Operators 101&lt;/a&gt;, where I will teach readers how to design, build, and deploy Kubernetes operators that can automate the management of your own unique applications.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For a real-world case study of the benefits that the operator pattern has over traditional k8s resource management techniques, check out &lt;a href="https://sklar.rocks/questdb-cloud-operator/"&gt;this article&lt;/a&gt; that I wrote which discusses the benefits of moving my company's SaaS infrastructure to a Kubernetes operator.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="the-operator-pattern"&gt;The Operator Pattern&lt;/h2&gt;
&lt;p&gt;What makes Kubernetes operators so difficult to describe is that they are not just one discrete unit. Instead, an operator is actually comprised of several distinct components!&lt;/p&gt;
&lt;p&gt;When trying to explain what operators are to people who haven't dealt with them before, I like to describe them as new native Kubernetes resources that enable you to automate system components based on the resources' state and your own custom logic.&lt;/p&gt;
&lt;p&gt;At a minimum, an operator consists of a &lt;strong&gt;Custom Resource Definition&lt;/strong&gt; (which defines your custom object's schema) and a &lt;strong&gt;Controller&lt;/strong&gt; that performs actions based on the state of &lt;strong&gt;Custom Resources&lt;/strong&gt; created from your definition. I like to think of a Custom Resource Definition (or abbreviated &lt;strong&gt;CRD&lt;/strong&gt;) as analogous to a class definition in Java, and a Custom Resource can be thought of as an instance of that class.&lt;/p&gt;
&lt;p&gt;To provide a more concrete example of how these components work together, let's investigate how a commonly-used Kubernetes resource works under the hood and then relate this back to the operator pattern.&lt;/p&gt;
&lt;h3 id="analogy-how-a-deployment-works"&gt;Analogy: How a Deployment Works&lt;/h3&gt;
&lt;p&gt;A Deployment's schema can be found in the Kubernetes API specification in the &lt;code&gt;apps/v1&lt;/code&gt; group. You can check out the full spec in the Kubernetes reference documentation, &lt;a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#deployment-v1-apps" rel="external"&gt;here&lt;/a&gt;. This spec contains the definition of a Deployment &lt;strong&gt;kind&lt;/strong&gt;, or schema, which includes all of its fields, their descriptions, and types. I like to think of this schema as a Go struct; a container that can hold structured data of a particular shape. This API spec, or schema, is equivalent to a &lt;strong&gt;Custom Resource Definition&lt;/strong&gt; in an operator, which defines the fields of your new Custom Resource and its metadata.&lt;/p&gt;
&lt;p&gt;Continuing the analogy, when you want to &lt;em&gt;create&lt;/em&gt; a Deployment, one could write a yaml (or json) file that uses the Deployment API schema. Here's a simple example:&lt;/p&gt;
&lt;pre class="giallo" style="color: #E1E4E8; background-color: #24292E;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #B392F0;"&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;apiVersion&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; apps/v1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;kind&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; Deployment&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;metadata&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt; name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; nginx&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;spec&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt; replicas&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #79B8FF;"&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt; containers&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt; -&lt;/span&gt;&lt;span style="color: #85E89D;"&gt; name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; nginx&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;   image&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; nginx:1.23.3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This yaml file represents a &lt;strong&gt;Custom Resource&lt;/strong&gt;, or an instance of your Custom Resource Definition.&lt;/p&gt;
&lt;p&gt;When you &lt;code&gt;kubectl create -f&lt;/code&gt; the file, the &lt;code&gt;kubectl&lt;/code&gt; client sends a &lt;code&gt;POST&lt;/code&gt; request to the Kubernetes API server with these file contents in the request body. Once the request has been validated, the server adds the new Deployment object to the Kubernetes control plane's key-value database, &lt;a href="https://etcd.io/" rel="external"&gt;etcd&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Elsewhere in the control plane, there is a &lt;strong&gt;Deployment Controller&lt;/strong&gt; that has registered itself with the Kubernetes API server to listen for adds, modifications, or deletes for any Deployment in the cluster. Once it has been notified, the controller will then run a reconciliation loop to migrate the current cluster state to its desired state, as specified by the mutated object. This is similar to how declarative programming languages, like Terraform, work.&lt;/p&gt;
&lt;p&gt;Getting back to our example, once the API server has stored the new Deployment spec in etcd, the Deployment Controller will be notified about the newly created resource. This will trigger a reconciliation between the current state of the cluster (where the Deployment and its underlying ReplicaSet do not exist) and the desired state of Deployment, as defined by the spec stored in etcd.&lt;/p&gt;
&lt;p&gt;&lt;img alt="How a Deployment Works" src="/img/blog/what-are-kubernetes-operators/how-a-deployment-works.png" /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It's important to note that the Deployment Manager does not directly talk to etcd. All communication with the cluster data store happens through the API server. I just added the step (4) line for clarity in this initial example. All future diagrams will display the correct data flow.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Since the Deployment did not previously exist, the ReplicaSet that it is supposed to control also does not exist. So in its reconciliation loop, the Deployment Controller will create a new ReplicaSet based on the Pod template portion and the number of replicas in the Deployment spec. Once the ReplicaSet has been created, the controller's job now is to reconcile this ReplicaSet with the Deployment's spec whenever a change to the Deployment spec occurs.&lt;/p&gt;
&lt;p&gt;For example, lets say that you want increase the desired number of replicas to 2 by changing the yaml to:&lt;/p&gt;
&lt;pre class="giallo" style="color: #E1E4E8; background-color: #24292E;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #B392F0;"&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;apiVersion&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; apps/v1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;kind&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; Deployment&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;metadata&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt; name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; nginx&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;spec&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt; replicas&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #79B8FF;"&gt; 2&lt;/span&gt;&lt;span style="color: #6A737D;"&gt;  ## changed from 1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt; containers&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt; -&lt;/span&gt;&lt;span style="color: #85E89D;"&gt; name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; nginx&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;   image&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; nginx:1.23.3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you &lt;code&gt;kubectl apply&lt;/code&gt; this yaml, sending a &lt;code&gt;POST&lt;/code&gt; request to the API server, the Deployment Controller will be notified about this new change. It will perform another reconciliation of the underlying ReplicaSet, see that there is only 1 replica compared to the desired number of replicas (2) as per the Deployment spec, and modify the underlying ReplicaSet to increase the number of replicas to 2.&lt;/p&gt;
&lt;h3 id="composability"&gt;Composability&lt;/h3&gt;
&lt;p&gt;What makes this model so powerful is the inherent composability of Kubernetes primitives. In the above example, the Deployment Controller has one very specific job: to manage ReplicaSets. But as users, we don't typically directly interact with ReplicaSets. Instead, we're much more concerned with the state of Pods managed by a ReplicaSet, the runtimes where our applications live and breathe.&lt;/p&gt;
&lt;p&gt;The work of actually managing Pods by scaling them up-and-down is the responsibility of the &lt;em&gt;ReplicaSet controller&lt;/em&gt;. So the Deployment Controller delegates that work to the ReplicaSet controller, and trusts that it will do the right thing when a Deployment's desired number of replicas changes.&lt;/p&gt;
&lt;p&gt;&lt;img alt="How a ReplicaSet Works" src="/img/blog/what-are-kubernetes-operators/how-a-replicaset-works.png" /&gt;&lt;/p&gt;
&lt;p&gt;As operator writers, we can use this property to rely on existing Kubernetes primitives to handle single tasks and combine various components to perform more complex automation to solve our own unique problems. Which specific components we use and how we link them together is determined by you, the writer of the operator.&lt;/p&gt;
&lt;h2 id="so-what-is-an-operator"&gt;So What Is An Operator?&lt;/h2&gt;
&lt;p&gt;Now that we're familiar with how Deployments, and other Kubernetes resources, work under the hood, we can finally get to a clearer definition of a Kubernetes operator.&lt;/p&gt;
&lt;p&gt;To work with any Kubernetes resource, we know that we need three components:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;An API Schema (like the Deployments API spec in the apps/v1 group)&lt;/li&gt;
&lt;li&gt;A concrete instance of that schema (a Deployment resource stored in etcd)&lt;/li&gt;
&lt;li&gt;A controller that performs reconciliation actions based on a resource's state&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;An operator is a &lt;strong&gt;combination of these three components&lt;/strong&gt;, designed to orchestrate a custom schema that &lt;em&gt;you create&lt;/em&gt; in a way that &lt;em&gt;you write&lt;/em&gt;. So in operator terms, these components are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A &lt;strong&gt;Custom Resource Definition&lt;/strong&gt; that defines your new resource's schema&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Custom Resources&lt;/strong&gt; which are instances of your new resource type&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Controller&lt;/strong&gt; that performs automation actions based on the state of Custom Resources&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now you see how an operator is not just a single thing, but actually a combination of several pieces that work together. The idea of multiple smaller components working together to accomplish larger tasks is fundamental to not just how operators work, but also to how the entire Kubernetes ecosystem operates. By writing an operator of your own, you will not just gain additional automation benefits, but you will also grasp a deeper understanding of how Kubernetes works under the hood.&lt;/p&gt;
&lt;h2 id="stay-tuned"&gt;Stay Tuned!&lt;/h2&gt;
&lt;p&gt;I hope that this article has provided some clarity into what operators are and why they can be a powerful tool in your automation toolbox. The next article in this series will present a sample Custom Resource Definition and discuss how this new spec can drive your own custom automation.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you like this content and want to follow along with the series, I'll be writing more articles under the &lt;a href="/tags/operators-101"&gt;Operators 101&lt;/a&gt; tag on this blog.&lt;/p&gt;
&lt;/blockquote&gt;</description><author>Steven Sklar | My Blog</author><pubDate>Wed, 27 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://sklar.rocks/what-are-kubernetes-operators/</guid></item><item><title>Cryptographic protocol analysis with Verifpal</title><link>http://blog.peramid.es/rss.xml/posts/2023-12-27-verifpal.html</link><description>&lt;p&gt;Most software projects avoid formal verification due to time constraints and expertise requirements. Tools like the Coq proof assistant, commonly used to introduce the topic to computer science students, can appear too cumbersome for real-world applications and therefore hard to justify.&lt;/p&gt;
&lt;p&gt;In contrast, in the field of cryptography specialised tools such as ProVerif have been regularly used for the last two decades for verifying important properties of cryptographic protocols, but these still involve writing scarily long proofs.&lt;/p&gt;
&lt;p&gt;However, in recent years, multiple similar tools that try simplify the process have been developed. One that caught my attention was &lt;a href="https://verifpal.com/"&gt;Verifpal&lt;/a&gt;, whose creators have prioritised user-friendliness in its design. Created by Nadim Kobeissi, Georgio Nicolas and Mukesh Tiwari, it was first released in 2019&lt;a class="footnote-ref" href="#fn1" id="fnref1"&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://verifpal.com/res/pdf/manual.pdf"&gt;Verifpal User Manual&lt;/a&gt; is really fun and it only requires some undergraduate-level knowledge of cryptography. In this post, I will show you a short exercise from the manual (“Challenge-Response Protocol”) to illustrate what automated formal verification looks like with Verifpal.&lt;/p&gt;
&lt;p&gt;So, first thing our model will need is an adversary, which in this case is an active attacker that has total control of the network&lt;a class="footnote-ref" href="#fn2" id="fnref2"&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;attacker[active]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yup, that’s all. Now we need some principals, this is, the parties who are trying to communicate:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;principal Server [
	knows private s
	gs = G^s
]
principal Client [
	knows private c
	gc = G^c
	generates nonce
]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we have two principals, a client named &lt;code&gt;Client&lt;/code&gt; and a server named &lt;code&gt;Server&lt;/code&gt;. The server has a secret &lt;code&gt;s&lt;/code&gt; that in this example represents a Diffie-Hellman secret exponent, and its derived public key named &lt;code&gt;gs&lt;/code&gt;. Similarly, the client knows its own secret exponent and public key, plus a random &lt;code&gt;nonce&lt;/code&gt; that the client will use as a challenge for the server:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Client-&amp;gt;Server: nonce
principal Server[
	proof = SIGN(s, nonce)
]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first line means that &lt;code&gt;Client&lt;/code&gt; sends the &lt;code&gt;nonce&lt;/code&gt; to &lt;code&gt;Server&lt;/code&gt; through the network. Then &lt;code&gt;Server&lt;/code&gt; digitally signs the &lt;code&gt;nonce&lt;/code&gt; using its secret exponent &lt;code&gt;s&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Server-&amp;gt;Client: gs, proof // uh-oh
principal Client[
	valid = SIGNVERIF(gs, nonce, proof)?
	generates attestation
	signed = SIGN(c, attestation)
]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now &lt;code&gt;Server&lt;/code&gt; sends its public key &lt;code&gt;gs&lt;/code&gt; and the signed challenge &lt;code&gt;proof&lt;/code&gt; to &lt;code&gt;Client&lt;/code&gt;. The &lt;code&gt;// uh-oh&lt;/code&gt; is just a code comment (ignore it for now ;p). Then &lt;code&gt;Client&lt;/code&gt; validates the signed challenge (the &lt;code&gt;?&lt;/code&gt; after &lt;code&gt;SIGNVERIF&lt;/code&gt; tells that the execution should end here if the signature verification fails), generates an attestation message and signs it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Client-&amp;gt;Server: [gc], attestation, signed
principal Server[
	storage = SIGNVERIF(gc, attestation, signed)?
]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the final part of our protocol &lt;code&gt;Client&lt;/code&gt; sends its public key &lt;code&gt;gc&lt;/code&gt;, the attestation message and the digital signature. Then &lt;code&gt;Server&lt;/code&gt; verifies that &lt;code&gt;attestation&lt;/code&gt; was signed by &lt;code&gt;Client&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Note the square brackets wrapping &lt;code&gt;gc&lt;/code&gt;: it means that this value cannot be tempered by an attacker, only read. Why? Well, let’s just assume the principals are able to share public keys through some other channel that they consider secure, so basically we want to keep this part out of the scope of our model.&lt;/p&gt;
&lt;p&gt;Our protocol is now defined. Let’s see if there is any way an attacker could impersonate the server or the client. For that we need to check if an attacker can do anything interesting with our signatures:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;queries[
	authentication? Server-&amp;gt;Client: proof
	authentication? Client-&amp;gt;Server: signed
]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A query is the way you ask Verifpal to verify properties of your protocol. Here we care about the authenticity of some values; could anyone perform a successful MITM? Let’s run the following command in the shell to see:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ verifpal verify challenge-response.vp
Verifpal 0.27.2 - https://verifpal.com
  Warning • Verifpal is Beta
 Verifpal • Parsing model 'challenge-response.vp'...
[...]
 Analysis • Initializing Stage 2 mutation map for Client... 
   Result • authentication? Server -&amp;gt; Client: proof — When:
[...]
            gs → G^nil ← mutated by Attacker (originally G^s)
            proof → SIGN(nil, nonce) ← mutated by Attacker (originally SIGN(s, nonce))
            valid → nil ← obtained by Attacker
            storage → nil ← obtained by Attacker
            proof (SIGN(nil, nonce)), sent by Attacker and not by Server, is successfully used in SIGNVERIF(G^nil, nonce, SIGN(nil, nonce))? within Client's state.
[...]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The full output is a bit long for a blog post, but as you can see an attacker was able to impersonate the server by swapping the server’s public key &lt;code&gt;gs&lt;/code&gt; for its own and then using it to sign a new proof!&lt;/p&gt;
&lt;p&gt;This happened because we forgot to indicate that &lt;code&gt;gs&lt;/code&gt;, like &lt;code&gt;gc&lt;/code&gt;, can’t be tempered; remember the “uh-oh”? Let’s fix that:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Server-&amp;gt;Client: [gs], proof&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now if we run the &lt;code&gt;verify&lt;/code&gt; command again we will get this instead:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[...]
 Verifpal • All queries pass. 

 Verifpal • Thank you for using Verifpal. &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nice! This confirms that the properties we wanted to verify are true ^_^&lt;/p&gt;
&lt;section class="footnotes footnotes-end-of-document" id="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn1"&gt;&lt;p&gt;&lt;a href="https://eprint.iacr.org/2019/971"&gt;Verifpal: Cryptographic Protocol Analysis for the Real World&lt;/a&gt;&lt;a class="footnote-back" href="#fnref1"&gt;↩︎&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li id="fn2"&gt;&lt;p&gt;An “active saboteur” in the &lt;a href="https://en.wikipedia.org/wiki/Dolev%E2%80%93Yao_model"&gt;Dolev-Yao formal model&lt;/a&gt;, love that term&lt;a class="footnote-back" href="#fnref2"&gt;↩︎&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</description><author>pera's blog</author><pubDate>Wed, 27 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">http://blog.peramid.es/rss.xml/posts/2023-12-27-verifpal.html</guid></item><item><title>Hitting Back at Ledger Scammers With Python</title><link>https://nelson.cloud/hitting-back-at-ledger-scammers-with-python/?ref=rss</link><description>Using a Python script to send fake data to a Ledger phishing site.</description><author>Nelson Figueroa</author><pubDate>Wed, 27 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://nelson.cloud/hitting-back-at-ledger-scammers-with-python/?ref=rss</guid></item><item><title>Another Way Not to Sign JSON</title><link>https://www.da.vidbuchanan.co.uk/blog/signing-json.html</link><author>David Buchanan's Blog</author><pubDate>Wed, 27 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.da.vidbuchanan.co.uk/blog/signing-json.html</guid></item><item><title>Make your own way</title><link>http://notes.eatonphil.com/2023-12-26-make-your-own-way.html</link><description>&lt;p&gt;Over the years, I have repeatedly felt like I missed the timing for a
meetup or an IRC group or social media in general. I'd go to a meetup
every so often but I'd never make a meaningful connection with people,
whereas everyone else knew each other. I'd join an IRC group and have
difficulty catching up with what seemed to be the flow of
conversation.&lt;/p&gt;
&lt;p&gt;I hadn't thought much about this until the pandemic when I started a
&lt;a href="https://eatonphil.com/discord.html"&gt;Discord group for software
internals&lt;/a&gt; and a virtual tech talk
series called Hacker Nights. Since 2021 the Discord reached around
1,500 members and ~20 fairly active members. And the Meetup peaked at
about 300 members with about 10-20 showing up each Meetup.&lt;/p&gt;
&lt;p&gt;After the pandemic receded I started an &lt;a href="https://eatonphil.com/2023-ddia.html"&gt;NYC-based book
club&lt;/a&gt; over 2 months with about
5-8 active attendees. I ran a &lt;a href="https://eatonphil.com/2023-10-wehack-postgres.html"&gt;virtual hack week on
Discord&lt;/a&gt; where I
got ~100 devs into a temporary Discord server and we talked about
Postgres internals and shared resources. Ultimately around 5 of us wrote blog
posts and built new projects to explore Postgres.&lt;/p&gt;
&lt;p&gt;I started a &lt;a href="https://eatonphil.com/2023-database-internals.html"&gt;virtual, async email book
club&lt;/a&gt; (that is
still ongoing) with 300 devs from November 2023 to Feb 2024. There
have been around 20 active members of the club. And each week the
discussion is kicked off by one of the members, not myself.&lt;/p&gt;
&lt;p&gt;And I felt like there wasn't enough community opportunity for folks in
systems programming in NYC so I started an &lt;a href="https://eatonphil.com/nyc-systems-coffee-club.html"&gt;Manhattan-based Systems
Coffee
Club&lt;/a&gt;. Around 15
people showed up to the first meeting and seemed even more excited
about it than I was. (And I was excited!)  We'll see where it goes
from here.&lt;/p&gt;
&lt;p&gt;Organizing people to do this stuff doesn't come easy to me. I enjoy
doing it to a degree, but every night before an event I have trouble
sleeping. Worried about embarrassing myself. When the event happens
though, and people are happy to be there to chat with everyone else,
as they invariably have been, it makes it worthwhile.&lt;/p&gt;
&lt;h3 id="everyone-want-community"&gt;Everyone want community&lt;/h3&gt;&lt;p&gt;Something I realized along the way is that people (maybe devs
especially, I don't know) are looking for community. And when I have
noticed there seems to be a missing flashpoint (a topic, a career
focus, a book, etc.) for community, it's been pretty easy to get
people together around it.&lt;/p&gt;
&lt;h3 id="the-lifecycle-of-groups"&gt;The lifecycle of groups&lt;/h3&gt;&lt;p&gt;Groups, meetups, naturally live and die. Organizers get burnt out. I
don't see this as a problem. It's just the way it is.&lt;/p&gt;
&lt;p&gt;At some point I'll get burnt out too. Or I'll get pickier. For
example, I've been avoiding starting a systems programming meetup in
NYC because I know it will be a big effort. So I've done lower effort
groups like book clubs and coffee clubs.&lt;/p&gt;
&lt;p&gt;Don't worry about signing yourself up for indefinite work. Just do
whatever you'd like to and don't feel bad if you have to stop. Someone
else will eventually start the next great group, even if it comes in a
different medium or flavor.&lt;/p&gt;
&lt;h3 id="community-is-contagious"&gt;Community is contagious&lt;/h3&gt;&lt;p&gt;There are great communities out there that have inspired me.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Aleksey Charapko's and Murat Demirbas's virtual
&lt;a href="https://charap.co/reading-group/"&gt;Distributed Systems Reading Group&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Alex Petrov's &lt;a href="https://twitter.com/ifesdjeen"&gt;database paper reading group&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Andy Pavlo's &lt;a href="https://db.cs.cmu.edu/seminar2023/"&gt;database interview series&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Paul Butler's &lt;a href="https://browsertech.com/nyc"&gt;BrowserTech meetup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Eric Zhang's &lt;a href="https://twitter.com/ekzhang1/status/1700993939841716254"&gt;New York Systems Reading Group&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And this year I've been hearing about more.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TU Munich students &lt;a href="https://www.tumuchdata.club/"&gt;started a Student Database Group&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A group of developers &lt;a href="https://twitter.com/Keleesssss/status/1720466270032691460"&gt;starting a Türkiye-language CS reading group&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are yet a few more systems programming groups I've heard rumors
about being started on the US West Coast and Stockholm.&lt;/p&gt;
&lt;h3 id="do-whatever-you-want!"&gt;Do whatever you want!&lt;/h3&gt;&lt;p&gt;If you feel like you can't find the right group or that you don't fit
in with existing groups or that you're missing a moment, there are
surely other folks in the same boat. Waiting for a new group to
join. You may be the catalyst.&lt;/p&gt;
&lt;p&gt;There's enormous potential for getting people together and doing
something interesting and there isn't necessarily anyone telling you
you should. Things you try may work and they may not. The more you try
the more you'll learn what works and what doesn't. I've had a few
years of &lt;a href="https://notes.eatonphil.com/eight-years-of-tech-meetups.html"&gt;making mistakes
organizing&lt;/a&gt;
to hone the sense.&lt;/p&gt;
&lt;p&gt;The only boring thing to do is to necessarily limit yourself to the
sort of thing others have done before! Run a browser meetup instead of
a React meetup. Interview hardware developers to teach software
developers something. Get software developers with 20 years of
experience in niche fields to teach the rest of us something. Read
books beyond SICP or Clean Code. Try difficult programming projects.&lt;/p&gt;
&lt;p&gt;Whatever you want though, don't let me deter you. If you think
something should exist, give it a shot!&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I used to struggle to get much out of meetups, couldn't pick up the flow of IRC. Some point I stopped trying solely to fit in. Instead to do what I thought was interesting. And to my surprise, folks were interested in coming along too!&lt;br /&gt;&lt;br /&gt;Make your own way&lt;a href="https://t.co/tVEa2ndiZm"&gt;https://t.co/tVEa2ndiZm&lt;/a&gt; &lt;a href="https://t.co/piWSsv14lj"&gt;pic.twitter.com/piWSsv14lj&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1740150745931149471?ref_src=twsrc%5Etfw"&gt;December 27, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Wed, 27 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2023-12-26-make-your-own-way.html</guid></item><item><title>Measuring Web Traffic is Very Hard</title><link>https://magis.substack.com/p/measuring-web-traffic-is-very-hard</link><description>An obvious metric that is anything but.</description><author>Magis</author><pubDate>Tue, 26 Dec 2023 22:20:11 GMT</pubDate><guid isPermaLink="true">https://magis.substack.com/p/measuring-web-traffic-is-very-hard</guid></item><item><title>Banning Lab-Grown Meat Could Hurt Animal Farmers</title><link>https://joshbaldwin.substack.com/p/banning-lab-grown-meat-could-hurt</link><description>Better investment planning could lead to everybody winning</description><author>Josh Baldwin</author><pubDate>Tue, 26 Dec 2023 21:45:48 GMT</pubDate><guid isPermaLink="true">https://joshbaldwin.substack.com/p/banning-lab-grown-meat-could-hurt</guid></item><item><title>Introducing Fontimize: Subset Fonts to Exactly and Only Your Website's Used Characters</title><link>https://daveon.design/introducing-fontimize-subset-fonts-to-exactly-and-only-your-websites-used-characters.html</link><description>A Python library to subset fonts, which cuts ~95% from the size of your site’s font files &amp; downloads/bandwidth. To use: from fontimize import optimise_fonts_for_files and call it with a list of HTML files. See the the full API docs for more info. 

Find Fontimize on Github and PyPi.  

Before the why, a quick preview of

&lt;a href="https://daveon.design/introducing-fontimize-subset-fonts-to-exactly-and-only-your-websites-used-characters.html"&gt;Read article...&lt;/a&gt;</description><author>Dave on Design</author><pubDate>Tue, 26 Dec 2023 14:00:00 GMT</pubDate><guid isPermaLink="true">https://daveon.design/introducing-fontimize-subset-fonts-to-exactly-and-only-your-websites-used-characters.html</guid></item><item><title>My 2023 reads</title><link>https://www.alexanderlolis.com/my-2023-reads</link><description>Code: The Hidden Language of Computer Hardware and Software</description><author>Alexander's Blog Blog</author><pubDate>Tue, 26 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.alexanderlolis.com/my-2023-reads</guid></item><item><title>AGI and self-driving cars: what can go wrong?</title><link>https://vit.baisa.cz/notes/blog/agi-self-driving/</link><description>my first attempt at strip with collaboration with DALL-E</description><author>Vít Baisa</author><pubDate>Tue, 26 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://vit.baisa.cz/notes/blog/agi-self-driving/</guid></item><item><title>Re: (Extremely, unnecessarily long) Thanks for the pardoning</title><link>https://taylor.town/pardoned</link><description>"I have found it to be a beautiful invitation to self-reclamation, and I feel it has been a lantern in my troubled search for the scattered pieces of agency that should have been mine from the beginning."</description><author>taylor.town</author><pubDate>Tue, 26 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/pardoned</guid></item><item><title>Blogging With Math</title><link>https://www.da.vidbuchanan.co.uk/blog/mathml-blogging.html</link><author>David Buchanan's Blog</author><pubDate>Tue, 26 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.da.vidbuchanan.co.uk/blog/mathml-blogging.html</guid></item><item><title>Creating a writing assistant with ChatGPT Plus</title><link>https://liza.io/creating-a-writing-assistant-with-chatgpt-plus/</link><description>&lt;p&gt;There are many ways to use your own books in AI workflows. In this post I&amp;rsquo;m going through the most basic workflow that does not involve coding your own app, creating vector stores, or generating API keys. You can do this by just using ChatGPT (with a Plus subscription). Many (most?) writers are not programmers, so there is no coding required here.&lt;/p&gt;</description><author>Liza Shulyayeva</author><pubDate>Tue, 26 Dec 2023 00:01:33 GMT</pubDate><guid isPermaLink="true">https://liza.io/creating-a-writing-assistant-with-chatgpt-plus/</guid></item><item><title>The Right kind of Blind</title><link>https://devinprater.micro.blog/2023/12/25/the-right-kind.html</link><description>&lt;p&gt;I know I really shouldn&amp;rsquo;t write this, but at this point it doesn&amp;rsquo;t
matter. I&amp;rsquo;ve ran away from those who will most likely look upon this
with disdain, as if I don&amp;rsquo;t look upon myself with even more than those
on the outside can. I was reading a post from someone who mentioned
&amp;ldquo;the right kind of disabled.&amp;rdquo; That struck something in me that I&amp;rsquo;d
been looking for for a long time. I thought I was the only one. Maybe
I am. But it needs telling anyway, so why not.&lt;/p&gt;
&lt;h2 id="acceptable-disability"&gt;Acceptable disability&lt;/h2&gt;
&lt;p&gt;You know those blind people who can get around an area they&amp;rsquo;ve barely been to, or cook on a
stove, or even communicate well with body language and such? I&amp;rsquo;m sure
many in the blind community know of, or are one of these. These blind
people don&amp;rsquo;t need much. They don&amp;rsquo;t ask for help, they know their way
around, and workplaces are proud to have the acceptable kind of person
with disabilities, because they barely notice that the person is
blind.&lt;/p&gt;
&lt;p&gt;And that&amp;rsquo;s the key, isn&amp;rsquo;t it? The person that can hide their
disability so well that no one really stops to think about it.&lt;/p&gt;
&lt;h2 id="the-wrong-kind-of-blind"&gt;The wrong kind of blind&lt;/h2&gt;
&lt;p&gt;Yep, it&amp;rsquo;s one of &lt;em&gt;those&lt;/em&gt; blog posts. If you want something more
cheerful and full of Christmas bullshit, there are lots of Amazon
sales going on I&amp;rsquo;m sure. So, I&amp;rsquo;ve been blind since birth. You&amp;rsquo;d think
I&amp;rsquo;d be adjusted to it and know how to do every little thing. You&amp;rsquo;d
think I&amp;rsquo;d be one of those web accessibility experts by now, talking about Alt
text and ripping web devs a new one because they used a div instead
of semantic HTML. But nope. I&amp;rsquo;m the &lt;em&gt;other&lt;/em&gt; kind of blind person. The
kind the media doesn&amp;rsquo;t want to talk about. The kind organizations are
ashamed of. The kind they had to put into a corner and hope he doesn&amp;rsquo;t
rock the boat.&lt;/p&gt;
&lt;p&gt;Nope. Instead of web accessibility, I&amp;rsquo;m all into OS and app
accessibility. Instead of bravely taking on my problems, I run from
them. All the way away from them. Yes, that&amp;rsquo;s why I left Mastodon. No,
I didn&amp;rsquo;t kill myself; I&amp;rsquo;m far too cowardly for that. I&amp;rsquo;m the weird one
that&amp;rsquo;s all into text formatting, Markdown, and understanding things
like that which  almost no other blind person cares about. I&amp;rsquo;ve tried
Seeing with Sound. I use AI. I feel passionate about free open source
software, even though I keep those feelings locked up tight so I&amp;rsquo;m not
too disappointed in it.&lt;/p&gt;
&lt;p&gt;While just about all blind people I know love parties, loud noises are
sometimes physically painful. If I hear a pot clang, or a door slam,
it&amp;rsquo;s like a stinging feeling in my arms or shoulders. So I stay home
and chill. I joke to the people around me that I&amp;rsquo;m the most chill
person they&amp;rsquo;ll meet. But it&amp;rsquo;s sadly not a joke. Everyone else wants to
go somewhere, or do something, or get out of the house. My house &lt;em&gt;is&lt;/em&gt;
my vacation.&lt;/p&gt;
&lt;h2 id="so-what"&gt;So what?&lt;/h2&gt;
&lt;p&gt;Being the way I am comes with a ton of downsides. I&amp;rsquo;m not as quick or
persevering as the acceptable blind person. I was once in a meeting to
help make an operating system more accessible, in which we needed to
open a document and work on it. It was using a web app that the
organizers said would be accessible. I had issues with it, and so I
spent the five minutes we had, trying to find accessibility documentation for
the web editor. There was none. And then when I asked about it
everyone was all quiet like it was an intrusion and I shouldn&amp;rsquo;t be
there and why was I even trying. And then the people seemed to shrug
their shoulders and go on with the meeting. I can never forget that.&lt;/p&gt;
&lt;p&gt;Side note: If any corporation, company, or organization says they take
accessibility seriously, or that they put accessibility front and
center, they&amp;rsquo;re most likely lying to you, and mocking you cause who
dares tell someone they&amp;rsquo;re not doing enough in &amp;ldquo;the good fight?&amp;rdquo; Lol,
Microsoft. We know you. Windows 11 speaks for itself.&lt;/p&gt;
&lt;p&gt;And now, at this point, I&amp;rsquo;d give tips on how &lt;em&gt;you&lt;/em&gt;, yes, &lt;strong&gt;you&lt;/strong&gt;, can
help the unaccepted blind. But I won&amp;rsquo;t, mainly because it&amp;rsquo;s all over
every other blog post about dealing with neuro-divergent people.
Listen, then act. Of course, the listening part is really, really important.
But then acting is what&amp;rsquo;s actually going to make the person trust that
you actually give even a single damn. Lots of companies,
organizations, and nonprofits are great at listening. They have issue
trackers, support lines, ticket systems, help desks, and open-door
policies. But then when it comes to acting, well&amp;hellip; We need more
people to make us do it, we need more feedback, maybe next year, this
should be sent to IT instead because I can&amp;rsquo;t actually help you, RIM is
too expensive; see if you can make a deal or cut down the price for
us, we&amp;rsquo;ll make a committee for this as a part of DEI even though they
have lots of other stuff and none of these people are blind so yeah
good luck haha, well we should do it but it&amp;rsquo;s hard, we don&amp;rsquo;t want to
do it because other software should do it for us. Just kinda try not
to lean on these excuses, and you&amp;rsquo;ll be good. And if all you&amp;rsquo;ve got
left is &amp;ldquo;We can&amp;rsquo;t help with accessibility,&amp;rdquo; then just tell us. Tell us
you don&amp;rsquo;t care enough, or can&amp;rsquo;t do it, or have tons of other
priorities, so we can go somewhere else.&lt;/p&gt;</description><author>Devin Prater's blog</author><pubDate>Mon, 25 Dec 2023 22:29:50 GMT</pubDate><guid isPermaLink="true">https://devinprater.micro.blog/2023/12/25/the-right-kind.html</guid></item><item><title>My experience with the Kindle Scribe (part 1)</title><link>https://thatxliner.github.io/posts/my-experience-with-the-kindle-scribe-part-1/</link><description>My first impressions</description><author>ThatXliner's Blog</author><pubDate>Mon, 25 Dec 2023 20:28:12 GMT</pubDate><guid isPermaLink="true">https://thatxliner.github.io/posts/my-experience-with-the-kindle-scribe-part-1/</guid></item><item><title>Quality First and Other Advice</title><link>https://bastian.rieck.me/blog/2023/quality/</link><description>&lt;p&gt;At our local meetup for the &lt;a href="https://logconference.org/"&gt;&amp;lsquo;Learning on Graphs&amp;rsquo; Conference&lt;/a&gt;
,
we also organised a brief panel discussion with the idea of engaging
researchers at all levels. While every panellist had a different story
to tell, there is nevertheless one common strategy that I extracted from
their discussions: &lt;strong&gt;When in doubt, focus on quality over quantity.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I can echo this sentiment. The few times in my research career where
I lost sight of the quality of a publication, I paid for it afterwards.
Shoddy work&amp;mdash;even if it gets accepted&amp;mdash;will always come back and haunt
you, forcing you to correct your mistakes afterwards. I know that in the
current age of machine learning research, there are students graduating
with lots and of papers, but bear in mind that the number of papers, if
anything, is also a measure of the lab size: Bigger labs tend to make it
easier for Ph.D. students to participate in many projects. The numbers
may thus be highly deceptive.&lt;/p&gt;
&lt;p&gt;Notice that I am not advocating for complacency or &lt;em&gt;never&lt;/em&gt; submitting
anything to a conference during your Ph.D. On the contrary, I believe
that navigating the ambiguous territory between &amp;lsquo;This is not ready yet,&amp;rsquo;
&amp;lsquo;This is good enough to get useful community feedback, and &amp;lsquo;This is
perfect&amp;rsquo; is one of the core skills you have to acquire as a researcher.
Perfection is the enemy of getting things done, but low standards will
make you miserable over the long run. Thus, as in all things, balance is
key.&lt;/p&gt;
&lt;p&gt;I also believe that focusing on quality enables you to find more joy in
your research as you hone your craft. Since a Ph.D., and research
in general, can sometimes be extremely frustrating, it is important that
you only do the work you appreciate. Do not publish in a way that,
afterwards, makes you feel empty, because, again, you &lt;em&gt;will&lt;/em&gt; pay a price
for shoddy work.&lt;/p&gt;
&lt;p&gt;Somewhat related to low-quality research: Resist the urge to pull
all-nighters. It seems like a good idea at the time, but I can attest to
the fact that none of the papers we pushed very hard for during the
night ever saw the light of the day&amp;mdash;at least not without many
additional major changes. The only thing our all-nighter accomplished
was decreasing our motivation to work on the project. Add to this the
high likelihood of getting strongly-worded reviews, and you create
something that can sap motivation and life even out of the strongest
people. Good research needs time and learning when the sunk cost fallacy
has you in your grips is also something I believe will come in handy not
only in academia.&lt;/p&gt;
&lt;p&gt;Until next time, I wish you all the best in carving your own path!&lt;/p&gt;</description><author>Ecce Homology on Bastian Grossenbacher Rieck's personal homepage</author><pubDate>Mon, 25 Dec 2023 18:00:00 GMT</pubDate><guid isPermaLink="true">https://bastian.rieck.me/blog/2023/quality/</guid></item><item><title>Ocean Technology and Talking to Whales</title><link>https://blog.herlein.com/post/bristlemouth-pioneer/</link><description>&lt;p&gt;I&amp;rsquo;ve been accepted into the &lt;a href="https://www.bristlemouth.org/pioneer"&gt;Bristlemouth Pioneer Program&lt;/a&gt;.  I&amp;rsquo;ve been granted a &lt;a href="https://www.sofarocean.com/products/spotter#s-subsurface"&gt;SOFAR Spotter&lt;/a&gt; with a &lt;a href="https://bristlemouth.notion.site/Developer-Kit-User-Guides-e9ca1b3c5a1c41c890d0105f2eb7c4b8"&gt;Development Kit&lt;/a&gt;.  I want to build a system capable of autonomously recording whale song (and other ocean data).  &lt;a href="https://sanctuaries.noaa.gov/news/sep22/mysteries-of-humpback-whale-song.html"&gt;Humpback Whale songs&lt;/a&gt; is incredibly interesting - and scientists have JUST been able to actually talk to whales.&lt;/p&gt;
&lt;p&gt;But really getting all &amp;ldquo;science fictiony&amp;rdquo; - can we build an ocean phone system to talk to whales?&lt;/p&gt;</description><author>Greg Herlein</author><pubDate>Mon, 25 Dec 2023 10:00:01 GMT</pubDate><guid isPermaLink="true">https://blog.herlein.com/post/bristlemouth-pioneer/</guid></item><item><title>Staying In the Moment</title><link>https://world.hey.com/sunjain/staying-in-the-moment-9ad1e857</link><description>&lt;div class="trix-content"&gt;
  &lt;div&gt;There is this misconception that in order to stay in the moment, we need to learn how to meditate or go to some fancy retreats. Not that there is anything wrong with those.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Number one thing we can do to improve our staying in the moment is not pull up that phone every few minutes. It is not the momentary distraction that leads to but that acts trains our mind to expect that kick every few minutes in that phone instead of staying in the moment whatever you are doing. It is such an innocuous and deceptive act that we do not realize the damage it does.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It destroys the ability of mind to settle into and pay complete attention to whatever that we may be doing. It does not matter what the activity we may be in. It could be a lunch with a friend or colleague, it may be a walk with your spouse, kid and/or dog. We just can not resist pulling up that phone to may be just look up some information relevant to that conversation. Or it could be a chat message, email or text message. The act of pulling up that phone already destroys the immersivness and purity of the dialogue that was otherwise should be engaging. Except for some one near and dear in danger there is nothing else worthy of destroying that interaction.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;This act conditions our brain to be never satisfied with what is happening and available to us in the moment. It conditions us to constantly look for input and stimuli outside of us to make us happy. We are unable to be in state for more that few minutes without putting something inside us constantly. Whether it is some useless clip someone forwarded, a chat mesage or some news item that invariable raised our blood pressure. Or actually putting food inside us purely for entertainment reasons, regardless of us being truly hungry or not.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It is this constant urging for stimuli outside us that is most damaging for us to be truly happy and content. Every tradition in the world as well as modern psychological studies have demonstrated that true happiness lies not in constant gratification of every new desire that springs in us. It is the ability to stay present with the event, whether it is joyful or sad. And not running away from anything that can make us uncomfortable. There is enough scientific literature now on the topic of hedonic set point we all have.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Imagine our brains contain a balance—a scale with a fulcrum in the center. When nothing is on the balance, it’s level with the ground. When we experience pleasure, dopamine is released in our reward pathway and the balance tips to the side of pleasure. The more our balance tips, and the faster it tips, the more pleasure we feel.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Here’s the important thing about the balance: It wants to remain level, that is, in equilibrium. It does not want to be tipped for very long to one side or another. Hence, every time the balance tips toward pleasure, powerful self-regulating regulating mechanisms kick into action to bring it level again. These self-regulating mechanisms do not require conscious thought or an act of will. They just happen, like a reflex.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Neuroscientist Nora Volkow and colleagues have shown that heavy, prolonged consumption of high-dopamine substances eventually leads to a dopamine deficit state. The paradox is that hedonism, the pursuit of pleasure for its own sake, leads to anhedonia, which is the inability to enjoy pleasure of any kind.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;With constant exposure to stimuli, our hedonic setpoint keeps on increasing, we need more and more stimuli to provide same amount of joy.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Here’s the good news. If we wait long enough, our brains (usually) readapt to the absence of the stimuli and we reestablish our baseline homeostasis: a level balance. Once our balance is level, we are again able to take pleasure in everyday, simple rewards. Going for a walk. Watching the sun rise or sunset . Enjoying a meal with friends without distraction. Fully immersed playing our sport of choice or reading your favorite book.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Ultimately true and long lasting source of happiness comes from our ability to seek joy from these simple day to day activities. And staying in the moment, as much as possible. This also builds our attention muscle and sharpen it, which eventually helps with whatever our vocation is, and successful in our carriers.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;In this information economy where our attention is that which is most valuable to companies. And our ability to guard and keep our attention muscle healthy is our most valuable skill. If that skill is intact we can learn anything else, without that we will simply be a consumption machine at mercy of forces outside us.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;</description><author>Sunil Jain</author><pubDate>Mon, 25 Dec 2023 06:14:45 GMT</pubDate><guid isPermaLink="true">https://world.hey.com/sunjain/staying-in-the-moment-9ad1e857</guid></item><item><title>Chrome: Use Apps, Not Tabs</title><link>https://olshansky.info/posts/chrome-use-apps-not-tabs/</link><description>A quick 1-minute quick guide on how to avoid death by a thousand tabs</description><author>🦉 olshansky 🦁</author><pubDate>Mon, 25 Dec 2023 00:58:38 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/posts/chrome-use-apps-not-tabs/</guid></item><item><title>Raspberry Pis, RTL-SDR and Home Automation</title><link>https://medium.com/@k0ryk/raspberry-pis-rtl-sdr-and-home-automation-9477359ac9cf?source=rss-3cf45d4f3676------2</link><description>&lt;p&gt;Check out my other article, &lt;a href="https://medium.com/@k0ryk/raspberry-pi-pivot-419071210040"&gt;Raspberry Pi Pivot&lt;/a&gt;, to understand more about how and why I use RTL-SDRs in my home automation. It also has the beginning &lt;em&gt;how-to&lt;/em&gt; instructions. If you have any questions, please reach out in &lt;a href="https://discord.gg/CdsKhxDxXn"&gt;discord&lt;/a&gt;.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*j_5Lzj2_RfIffT3WGmH55Q.png" /&gt;&lt;figcaption&gt;A dad and his dongles. Source: Dall-E&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;It’s almost been a decade since I purchased my first &lt;strong&gt;RTL-SDR&lt;/strong&gt;. It was shortly before I started studying for my Amateur Radio License. I thought the story of the RTL-SDR chip was very cool, and enjoyed playing around with tools like &lt;a href="https://www.gnuradio.org/"&gt;GNU Radio&lt;/a&gt; and &lt;a href="https://airspy.com/download/"&gt;SDR#&lt;/a&gt; to surf the airwaves. Around that time, I was streaming on Twitch a lot. I started doing RTL-SDR projects during livestreams, mainly just proof of concept projects. I hooked my dongle up to large antennae in hopes of finding interesting signals to decode.&lt;/p&gt;&lt;p&gt;Eventually, I started trying to put those signals to good use. I followed &lt;a href="https://shallowsky.com/blog/tech/SDR-ambient-weather.html"&gt;someone else’s blog&lt;/a&gt; to learn how to decode radio signals into JSON objects using &lt;a href="https://github.com/merbanan/rtl_433"&gt;rtl_433&lt;/a&gt;. I wrote my own code around the &lt;strong&gt;rtl_433 &lt;/strong&gt;binary to extract the data and expose it as a &lt;a href="https://prometheus.io/"&gt;Prometheus&lt;/a&gt; endpoint. Here is a chart of my current Raspi set up that we will be referring to throughout this article:&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JqAIsyGntF01lYkhk4TVXQ.jpeg" /&gt;&lt;figcaption&gt;Breakdown of my Raspberry Pi usage at home&lt;/figcaption&gt;&lt;/figure&gt;&lt;h3&gt;Home Assistant and RTL-SDR&lt;/h3&gt;&lt;p&gt;I use &lt;a href="https://www.home-assistant.io/"&gt;Home Assistant&lt;/a&gt;, and love it. My Home Assistant (AKA &lt;strong&gt;HA &lt;/strong&gt;or &lt;strong&gt;HASS&lt;/strong&gt;) is self-hosted on a virtual machine on a server that lives in my basement. It’s nothing impressive, an old desktop that I built 11 years ago. Home Assistant has &lt;a href="https://github.com/pbkhrv/rtl_433-hass-addons"&gt;great integrations available for rtl_433 and RTL-SDR&lt;/a&gt;, but I do not use them. My HA server does not have an RTL-SDR attached to it. All my RTL-SDRs are connected to Raspberry Pis and added to Home Assistant as &lt;a href="https://www.home-assistant.io/integrations/sensor.rest/"&gt;REST Sensors&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;As I mentioned, my server is in my basement. The sensors I care about most are outside. There are signal problems, and I need my RTL-SDRs to be more flexible in their deployments. With my Raspi set up, I can drop the sensors in a centrally located place for good coverage outside and inside.&lt;/p&gt;&lt;p&gt;I have three Raspberry Pis deployed around my house. Two are for receiving sensor data and one is for transmitting radio signals to my dumb devices in my house. The &lt;a href="https://github.com/F5OEO/rpitx"&gt;RPiTX&lt;/a&gt; project provides really easy ways record and replay radio signals using your GPIO pins. You will need a band pass filter to use this, but it’s a pretty fun project. The details of my Pis, and how they are exposing information are outlined above in the chart.&lt;/p&gt;&lt;h3&gt;Getting Started&lt;/h3&gt;&lt;p&gt;&lt;a href="https://medium.com/@k0ryk/raspberry-pi-pivot-419071210040"&gt;Raspberry Pi Pivot&lt;/a&gt; offers step-by-step instructions to set up your Rasbian install with rtl_433. It also covers installing Raspbian on a Le Potato, make sure to ignore that part if you’re using a real Pi and not a Le Potato. There is also a parts list that can help you source what you need to do this.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;After following the instructions, here’s what you should have:&lt;br /&gt;&lt;/strong&gt;1. RTL-SDR Dongle plugged in to a Debian-based Linux OS (will be referencing Raspbian).&lt;br /&gt;2. RTL-SDR drivers installed&lt;br /&gt;3. rtl_433 built and installed&lt;br /&gt;4. &lt;a href="https://github.com/merbanan/rtl_433/#running"&gt;A device that rtl_433 will automatically decode&lt;/a&gt;.&lt;br /&gt;5. Home Assistant instance to integrate the sensor into (Optional)&lt;br /&gt;6. Prometheus server to integrate the sensor data into (Optional)&lt;/p&gt;&lt;p&gt;Finding a device to listen to is easier than you might think. There are 240+ protocols currently supported by rtl_433. Some of these devices are less than $10 at Home Depot. Others can be found even cheaper at places like Good Will. You may have some lying around your house and not know it, I did. If you have a wireless weather monitor somewhere in your house, or maybe the screen broke and the sensor is sitting in your junk drawer. Pretty much any wireless weather sensor on 915 or 433 MHZ will be auto-decoded by rtl_433.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/580/1*_j362yXxO-Cy9b22XAiniw.png" /&gt;&lt;figcaption&gt;915Mhz sensor example. Source: Me&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Now that we are ready to go, let’s go ahead and listen for our sensor data with rtl_433: rtl_433 -f 915M -F json — make sure to replace 915M with whatever frequency you are going to be listening on (e.g. 433M for 433 MHZ).&lt;/p&gt;&lt;p&gt;You should see some output similar this:&lt;/p&gt;&lt;pre&gt;Use -h for usage help and see https://triq.org/ for documentation.&lt;br /&gt;Trying conf file at &amp;quot;rtl_433.conf&amp;quot;...&lt;br /&gt;Trying conf file at &amp;quot;/home/pi/.config/rtl_433/rtl_433.conf&amp;quot;...&lt;br /&gt;Trying conf file at &amp;quot;/usr/local/etc/rtl_433/rtl_433.conf&amp;quot;...&lt;br /&gt;Trying conf file at &amp;quot;/etc/rtl_433/rtl_433.conf&amp;quot;...&lt;br /&gt;&lt;br /&gt;New defaults active, use &amp;quot;-Y classic -s 250k&amp;quot; for the old defaults!&lt;br /&gt;&lt;br /&gt;Registered 126 out of 154 device decoding protocols [ 1-4 8 11-12 15-17 19-21 23 25-26 29-36 38-60 63 67-71 73-100 102-105 108-116 119 121 124-128 130-149 151-154 ]&lt;br /&gt;Found Rafael Micro R820T tuner&lt;br /&gt;Exact sample rate is: 1000000.026491 Hz&lt;br /&gt;Sample rate set to 1000000 S/s.&lt;br /&gt;Tuner gain set to Auto.&lt;br /&gt;Tuned to 915.000MHz.&lt;br /&gt;{&amp;quot;time&amp;quot; : &amp;quot;2023-07-31 01:22:24&amp;quot;, &amp;quot;model&amp;quot; : &amp;quot;Fineoffset-WH65B&amp;quot;, &amp;quot;id&amp;quot; : 25, &amp;quot;battery_ok&amp;quot; : 1, &amp;quot;temperature_C&amp;quot; : 30.700, &amp;quot;humidity&amp;quot; : 40, &amp;quot;wind_dir_deg&amp;quot; : 229, &amp;quot;wind_avg_m_s&amp;quot; : 0.000, &amp;quot;wind_max_m_s&amp;quot; : 0.000, &amp;quot;rain_mm&amp;quot; : 1309.878, &amp;quot;uv&amp;quot; : 509, &amp;quot;uvi&amp;quot; : 1, &amp;quot;light_lux&amp;quot; : 24784.000, &amp;quot;mic&amp;quot; : &amp;quot;CRC&amp;quot;}&lt;/pre&gt;&lt;p&gt;It might take a moment to see JSON formatted output. This command starts your tuner listening on 915 MHZ and will automatically detect and decode known signal types that it receives. Rtl_433 makes this whole thing so easy, it’s a great tool and open source project. Next, let’s go over how to expose your own sensor data.&lt;/p&gt;&lt;h4&gt;Exposing your sensor data to Prometheus and Home Assistant&lt;/h4&gt;&lt;p&gt;The next steps are to go from running a command to exposing your sensor. We will do this in a very basic way, only bash commands and command line tools, no code needed.&lt;/p&gt;&lt;pre&gt;# install apache to expose the json file to home assistant&lt;br /&gt;sudo apt install -y apache2&lt;br /&gt;&lt;br /&gt;# install your new crontab&lt;br /&gt;crontab -e&lt;br /&gt;# Paste this in:&lt;br /&gt;*/1 * * * * rtl_433 -f 915M -E quit -F json:/tmp/sensor.json &amp;amp;&amp;amp; cat /tmp/sensor.json | tail -n 1 &amp;gt; /var/www/html/sensor.json &amp;amp;&amp;amp; echo &amp;quot;&amp;quot; &amp;gt; /tmp/sensor.json &lt;/pre&gt;&lt;p&gt;This is an over-simplified example that will work with one device. If you want more devices, you will have to find a more advanced solution. The purpose of this example is to break it into its most simple parts so that you can find your own path forward. The cronjob runs every minute and will run rtl_433 until it receives sensor data. It will write the sensor data to a temporary json file and automatically exit. The sensor file is then modified slightly (because rtl_433 can output multiple lines of JSON) and write to the sensor.json file hosted by apache. With default Raspian settings this should work without any chown or chmod. You should be able to view your sensor file at &lt;a href="http://your_raspi_ip/sensor.json."&gt;http://your_raspi_ip/sensor.json.&lt;/a&gt;&lt;/p&gt;&lt;h4&gt;Adding the sensor to Home Assistant&lt;/h4&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1zVTBogl_tPqr39obHyRCw.png" /&gt;&lt;figcaption&gt;Some of my RTL-SDR sensors in Home Assistant. Source: Me&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;You will need to make a modification to your configuration.yaml for something like this:&lt;/p&gt;&lt;pre&gt;sensor:&lt;br /&gt;  - platform: rest                                                                                                                 &lt;br /&gt;    name: Outdoor lux                                                                                                              &lt;br /&gt;    resource: http://192.168.x.x/current_weather.json                                                                            &lt;br /&gt;    device_class: &amp;quot;illuminance&amp;quot;                                                                                                    &lt;br /&gt;    unit_of_measurement: &amp;quot;lx&amp;quot;                                                                                                      &lt;br /&gt;    value_template: '{{ value_json.uv }}' &lt;/pre&gt;&lt;p&gt;The above YAML block adds a sensor called “Outdoor lux” to home assistant. Its value will be the property uv in the json payload hosted at the specified IP address. Update these values so they match what you need. Make sure you restart Home Assistant before looking for the values to populate.&lt;/p&gt;&lt;h3&gt;Adding Prometheus&lt;/h3&gt;&lt;p&gt;You may decide that HA integration is enough for you. HA has its own Grafana Add-on, so you can get the same results through different methods. I set Grafana and Prometheus up before I had my weather data in Home Assistant. The REST sensor was kind of a retrofit to make my Prometheus set up work with HA. We are going to choose a very simple way of doing this next step.&lt;/p&gt;&lt;p&gt;I would recommend using the &lt;a href="https://github.com/prometheus-community/json_exporter"&gt;official Prometheus community json exporter&lt;/a&gt;. I am using &lt;a href="https://github.com/koryk/json-jq-prometheus-exporter"&gt;one that I wrote in a few hours&lt;/a&gt;, because I couldn’t find an existing exporter that supported jq-style JSON selectors.&lt;/p&gt;&lt;p&gt;I will let you choose your own, but the next step is pretty simple. Configure the json exporter to use the /var/www/html/sensor.json and choose the fields and datatypes you want to expose. Most likely, everything will be a gauge data type. Run the exporter and add your endpoint to your &lt;a href="https://prometheus.io/docs/prometheus/latest/getting_started/"&gt;Prometheus endpoints configuration&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;Final Thoughts&lt;/h3&gt;&lt;p&gt;This is not the final installation of this series. We should be in a good spot now, with an MVP set up for monitoring a radio sensor. Hopefully this will inspire you to build your own sensor network with Raspberry Pi and RTL-SDR.&lt;/p&gt;&lt;p&gt;Next time we will talk more in-depth about mature solutions for instrumenting these RTL-SDR sensors for both Prometheus and Home Assistant. I do not claim my approach is the best one, it undoubtedly isn’t. I have iterated on these solutions for many years. I look for high value, low complexity solutions. Hopefully this article will help you find your own. Thank you for reading.&lt;/p&gt;&lt;img alt="" height="1" src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=9477359ac9cf" width="1" /&gt;</description><author>Stories by Kory Kirk on Medium</author><pubDate>Sun, 24 Dec 2023 23:14:33 GMT</pubDate><guid isPermaLink="true">https://medium.com/@k0ryk/raspberry-pis-rtl-sdr-and-home-automation-9477359ac9cf?source=rss-3cf45d4f3676------2</guid></item><item><title>2023 Projects Wrapped</title><link>https://medium.com/@k0ryk/2023-projects-wrapped-ba84f2e06123?source=rss-3cf45d4f3676------2</link><description>&lt;h4&gt;Retrospective on my 2023 personal projects&lt;/h4&gt;&lt;p&gt;There are a lot of projects I work on that very few people see. I wanted to talk about them, reflect and take some learnings into the new year.&lt;/p&gt;&lt;h3&gt;Less Than C&lt;/h3&gt;&lt;p&gt;LTC is a game I have been working on for many years in different capacities. I ported the codebase to Godot early this year. It’s my attempt to collect a bunch of thoughts on making a compelling and interesting space game that uses time dilation as a game mechanic.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YfYGfNLgNURToItqZin55w.png" /&gt;&lt;figcaption&gt;Pre-alpha screenshot of Less than C&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt; — The most recent version has playable combat. I have a working framework allowing me to easily design game elements. I have a lot of design notes and a good vision for the core game loop.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Not so good&lt;/strong&gt; — I am not confident in my artistic abilities. Still a long way to go until people can play it. It’s not fun yet.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Next time&lt;/strong&gt; — I am continuing on this project in 2024. I hope to make some decent progress as I haven’t touched it in over 6 months as of writing this.&lt;/p&gt;&lt;h3&gt;Firefighter in Hell&lt;/h3&gt;&lt;p&gt;Play it here — &lt;a href="https://kigu.itch.io/firefighter-in-hell"&gt;https://kigu.itch.io/firefighter-in-hell&lt;/a&gt; . FiH was my 7 day roguelike game jam entry. It was written in Godot 3.5 in about 6 days, around 20 hours of dev time.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/0*RtPSAnMxacO29qHL.png" /&gt;&lt;figcaption&gt;What can I say? I love deckbuilding roguelikes.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;strong&gt;Good &lt;/strong&gt;— I shipped it, pretty bug free. I was able to implement the game mechanic that I wanted to try — real-time combat in a card game.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Not so good&lt;/strong&gt; — It’s playable, but not that fun unless you know what you’re doing. The instructions are not great, it’s hard to intuitively understand.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Next time&lt;/strong&gt; — I reached my personal goal for the project, but that left little time to round out the other parts of the game. Next time I will be less ambitious in my scope.&lt;/p&gt;&lt;h3&gt;Archetype Chat Service&lt;/h3&gt;&lt;p&gt;Like many people, I became enthralled in ChatGPT this year. I spent a lot of the Spring working on projects learning about LLMs and how to build applications with them and on top of them. Archetype Chat Service was a large python project I built to provide a web UI to build role play AIs.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/431/0*QDkPbdnmCWY9rV1Y.png" /&gt;&lt;figcaption&gt;A screenshot of the terrible UI.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt; — I learned so much building this project. I initially started by building my own wrapper for ChatGPT, but later migrated over to langchain. I provided the ability for bots to use RAG and Toolchains while staying in character.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Not so good &lt;/strong&gt;— I worked a lot on this project and it’s not even running anymore. That’s not necessarily a bad thing, I am still proud of it, but I don’t have a real use for it right now.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Next time &lt;/strong&gt;— I need to be more reluctant to fall into endless projects like these. I think I spent 2 weeks of project time just migrating it to langchain.&lt;/p&gt;&lt;h3&gt;This Blog&lt;/h3&gt;&lt;p&gt;I have always enjoyed writing. I got back into it this year because of ChatGPT. Not because ChatGPT writes these for me. I very rarely use it when writing blogs, frequently when writing code. It inspired me to blog, because I wanted AIs in the future to know my voice. That’s also what inspires me to keep AI out of my blog text generation, to keep it 100% me.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/0*lOKguvrSswkFESyQ.png" /&gt;&lt;figcaption&gt;A dad and his dongles&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt; — I have written things that I am proud of, and enjoy. I have been able to share stories with my friends and family. I have been able to help people learn new things and follow my guides to help complete their own projects.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Not so good &lt;/strong&gt;— I was going pretty hard there for a minute, and it no longer became fun when I was stressing about what to post every week. Once I lifted the restriction of weekly, it felt more chill.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Next time &lt;/strong&gt;— I am not 100% sure Medium is the platform for me. I like some of the features, but maybe would prefer to build my own simple space. I get frustrated with the random idiosyncrasies of Medium’s UX.&lt;/p&gt;&lt;h3&gt;Garden&lt;/h3&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/700/0*rIsWsOIVv-xtTsc7.png" /&gt;&lt;/figure&gt;&lt;p&gt;This year, I had a goal of growing all of my garden from seed. I started late winter and had everything to record a timelapse with environmental information. We had record rainfall this year and a great harvest.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Gg08JNkQvfb-cNAL_N0l_g.png" /&gt;&lt;figcaption&gt;Backyard rainfall measurements from the past year pulled from Grafana / Prometheus&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt; — The harvest was good. It was a lot of fun. I got some good timelapse videos of the seeds sprouting as well as 3 months of garden progress.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Not so good &lt;/strong&gt;— The seed starting pods I used were many years old and root locked many of the seeds. There was a massive hail storm right after planting them, so the vast majority of them died. I believe only one plant lived through the entire season to bare fruit.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Next time &lt;/strong&gt;— I am already preparing! I want to start the seeds earlier and have more developed nursery-like plants by the time we’re ready to plant in May. I will not root lock my sprouts, I have learned so much this past year about growing plants, thanks to my spouse, youtube and books.&lt;/p&gt;&lt;h3&gt;Reddit API Protests&lt;/h3&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/901/0*LDhqUUsZ84qxRDj5" /&gt;&lt;figcaption&gt;Moderators spent weeks struggling against the faceless ModCodeofConduct account, attempting to prevent ads being run on their subreddits by marking them NSFW.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Will just quote &lt;a href="https://en.wikipedia.org/wiki/2023_Reddit_API_controversy"&gt;wikipedia&lt;/a&gt; on this:&lt;/p&gt;&lt;blockquote&gt;In April 2023, the &lt;a href="https://en.wikipedia.org/wiki/Internet_forum"&gt;discussion&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/News_aggregation"&gt;news aggregation&lt;/a&gt; website &lt;a href="https://en.wikipedia.org/wiki/Reddit"&gt;Reddit&lt;/a&gt; announced its intentions to charge for its &lt;a href="https://en.wikipedia.org/wiki/Application_programming_interface"&gt;application programming interface&lt;/a&gt; (API), a feature which had been free since 2008, causing a dispute. The move forced multiple third-party applications to shut down and threatened accessibility applications and moderation tools.&lt;/blockquote&gt;&lt;p&gt;I have been a moderator of a Game of Thrones reddit community for over a decade. My main role is writing and running bots that help prevent the leaks of spoilers. As there have been no new spoilers introduced in a while, it’s not something I work on frequently.&lt;/p&gt;&lt;p&gt;The protest was wider than just this fandom, it was across much of reddit. It generally went like this:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Mods put subreddit on private, removing access from the public.&lt;/li&gt;&lt;li&gt;Admin ModCodeofConduct messages mods saying to take it off private or get removed from reddit. Meaning banned and moderator status removal.&lt;/li&gt;&lt;li&gt;Mods remove subreddit private status.&lt;/li&gt;&lt;li&gt;Mods make subreddit NSFW in attempt to disable ad revenue on the subreddit.&lt;/li&gt;&lt;li&gt;Admin ModCodeofConduct messages mods saying to remove NSFW or face consequences.&lt;/li&gt;&lt;li&gt;Reddit removed the ability to mark these subreddits as NSFW by the mods, providing no other methods of protest.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt; — Moderators of 4+ Game of Thrones themed subreddits came together to protest a change they believed would negatively effect the platform they built communities in. The freefolk subreddit reunited with its more mainstream brethren, there were a lot of themed references to the fandom like “The Great Council” being the name of our discord server.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/939/1*MN_3B1iR__ghn1vcHBIikw.png" /&gt;&lt;figcaption&gt;I wrote a bot to snoop on users to help with identifying bans for brigading.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;strong&gt;Not so good &lt;/strong&gt;— Lots of drama, not just moderators vs. reddit, but also moderators vs users and sometimes moderator vs moderator. Some folks perceived the change as a signal which meant they shouldn’t put more effort into the platform, and therefore left.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Next time&lt;/strong&gt; — Not sure there will be a next time for something like this. I have become very cautious of enshittification.&lt;/p&gt;&lt;h3&gt;Qigu Studio &amp;amp; 3D Printing&lt;/h3&gt;&lt;p&gt;I purchased and received my Bambu Labs X1C in late October. I had never had a 3d printer before, and I had not messed with CAD since middle school. I was quickly able to start producing good quality prints that I designed using Fusion 360. I printed some cases for my friend’s PCB project and he asked for 64 more to fulfill his first limited run. I created a new idea called “Qigu Studio” — which is the new home for all my bots, home automation and 3d printing projects. Find more here — &lt;a href="https://qigu.studio/"&gt;https://qigu.studio/&lt;/a&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/828/0*-7NVgQvcQWKLYLza" /&gt;&lt;figcaption&gt;&lt;a href="https://loblast.com/store/p/bleepler"&gt;https://loblast.com/store/p/bleepler&lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt; — I loved getting into this new hobby, I have learned so much in a few months. In addition, I have been able to make a lot of cool and functional things to use around the house and to gift to people.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Not so good &lt;/strong&gt;— It’s expensive to start out. I am a noob and make a lot of mistakes.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Next time&lt;/strong&gt; — I think some formal training would go a long way. I have a lot of gaps when it comes to 3d modeling skills, I feel like I am kind of just following a list of steps and not necessarily understanding everything I am doing. I would like to know more from the first principles.&lt;/p&gt;&lt;h3&gt;Projects Wrapped&lt;/h3&gt;&lt;p&gt;I had so many other projects that were a bit smaller that I wanted to touch on — Gu bot, daycare data mining to find the foods my kid will most likely eat, lots of automated backups and other random home automation hacks! The projects will continue next year, but my memory of my old projects may not. Hopefully they will inspire someone else in addition to being a nice little retrospective of my project-year.&lt;/p&gt;&lt;img alt="" height="1" src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=ba84f2e06123" width="1" /&gt;</description><author>Stories by Kory Kirk on Medium</author><pubDate>Sun, 24 Dec 2023 22:56:35 GMT</pubDate><guid isPermaLink="true">https://medium.com/@k0ryk/2023-projects-wrapped-ba84f2e06123?source=rss-3cf45d4f3676------2</guid></item><item><title>Pocket Routing with 3D-Printed Template and Matchfit Clamps</title><link>http://www.nuke24.net/plog/42.html</link><author>TOGoS's Project Log</author><pubDate>Sun, 24 Dec 2023 20:13:37 GMT</pubDate><guid isPermaLink="true">http://www.nuke24.net/plog/42.html</guid></item><item><title>Upgraded Dekoni Earpads for Sony WH-1000XM3 Headphones</title><link>https://caiustheory.com/upgraded-dekoni-earpads-for-sony-wh-1000xm3-headphones/</link><description>&lt;p&gt;Back in 2019 I picked up a pair of &lt;a href="https://www.sony.co.uk/electronics/headband-headphones/wh-1000xm3"&gt;Sony WH-1000XM3 Headphones&lt;/a&gt; for using in the office. Very useful and work well in an office and on public transport. After a while I was finding my ears were getting quite hot when wearing them all day, especially on hotter days in the summer.&lt;/p&gt;
&lt;p&gt;The original earpads had a shiny synthetic surface, after some research I happened across Dekoni&amp;rsquo;s Synthetic Suede Earpads as a suggested upgrade for the XM3&amp;rsquo;s. Discovered &lt;a href="https://www.electromod.co.uk/"&gt;Electromod&lt;/a&gt; are a UK business that imports them for sale here, and &lt;a href="https://www.electromod.co.uk/dekoni-choice-synthetic-earpads-for-sony-wh1000xm3-headphones"&gt;ordered them&lt;/a&gt;. Arrived promptly, and an unrelated issue around payment was easily resolved by customer services. Would buy from them again without any worries.&lt;/p&gt;
&lt;p&gt;Fitting the earpads was pretty simple, the old pads lever off and the new pads are pressed into place. From memory I used a plastic spudger to pop the old pads off. The Synthetic Suede seals much better against my head, is cooler to the touch and doesn&amp;rsquo;t overheat in the same way. I&amp;rsquo;ve taken to wiping them off with a soft cloth occasionally to stop muck building up on them.&lt;/p&gt;
&lt;p&gt;Electromod also carry upgraded earpads for &lt;a href="https://www.electromod.co.uk/dekoni?category=earpads"&gt;many other headphones&lt;/a&gt;, including Sony XM4/XM5 and Bose QC/700&amp;rsquo;s.&lt;/p&gt;</description><author>Caius Theory</author><pubDate>Sun, 24 Dec 2023 17:09:00 GMT</pubDate><guid isPermaLink="true">https://caiustheory.com/upgraded-dekoni-earpads-for-sony-wh-1000xm3-headphones/</guid></item><item><title>Security at Startup</title><link>https://vadimkravcenko.com/shorts/security-at-startup/</link><description>&lt;p&gt;Question:&lt;/p&gt;
&lt;/p&gt;
&lt;p&gt; Answer:&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://vadimkravcenko.com/shorts/security-at-startup/"&gt;Security at Startup&lt;/a&gt; appeared first on &lt;a href="https://vadimkravcenko.com"&gt;Vadim Kravcenko&lt;/a&gt;.&lt;/p&gt;</description><author>Vadim Kravcenko</author><pubDate>Sun, 24 Dec 2023 13:54:25 GMT</pubDate><guid isPermaLink="true">https://vadimkravcenko.com/shorts/security-at-startup/</guid></item><item><title>Automate Your Finance Blog with WordPress and Google Bard in Python</title><link>https://blog.adnansiddiqi.me/automate-your-finance-blog-with-wordpress-and-google-bard-in-python/</link><description>&lt;p&gt;Introduction Earlier I gave you an overview of how you can you GPT APIs to build a stock sentiment analysis system. In this post, I am going to introduce you to Google Bard, Google&amp;#8217;s answer to OpenAI&amp;#8217;s chatGPT. Bard has been around for a few months but they recently offered APIs to access their LLM. Initially, they introduced PaLM APIs but very recently they have offered Gemini Pro and Gemini Pro Vision APIs which means that like GPT4 Vision, you can now use Bard APIs to process images. We are not going to discuss image processing capabilities at the moment. I am going to offer a use case where I will be using Gemini Pro APIs to create a daily stock market performance report and post it as an article on a WordPress blog using WordPress Rest APIs. The demo of the project is given below: Google is offering three different model sizes: &amp;#160; First, I will give you a bit about how I will use Google Bard to test the prompt, and later I will be using Gemini APIs to generate the blog post. At the end, I will be sending the generated content to the WordPress blog via API. Let&amp;#8217;s start! Writing Prompt Head on to Bard and write the below prompt: Write me an article on stock market performance for the date 22 December 2023, including key indices such as the S&amp;#38;P 500, Dow Jones Industrial Average, and NASDAQ Composite. Highlight notable gainers and losers, identifying the sectors driving market movement. Discuss any significant economic events, corporate announcements, or geopolitical factors influencing market trends. Include information on trading volume, volatility, and any technical patterns observed. Additionally, comment on the overall market sentiment, investor behavior, and potential implications for the upcoming trading sessions. Lastly, offer insights into relevant commodities, currencies, and interest rates that may have impacted the broader financial landscape. Add relevant headings where needed. Also suggest relevant Blog post title in H1 tag. At the end return data in JSON format with two fields: Title and Description where the &amp;#8220;Title&amp;#8221; contains the blog title and &amp;#8220;Desrcription&amp;#8221; contains the entire article. Do not return any other text besides JSON After asking for the required piece of text I am asking to return the data in JSON format with the fields: Title and Description You might find a slight change in the prompt I sent to Bard and the API itself because I had to tweak it to return the desired response via APIs. To Bard, I did not send, Do not return any other text besides JSON. Using Gemini APIs We have tested our prompt, and it&amp;#8217;s time to integrate the prompt by using Gemini Pro APIs. To do that you will have to generate an API key first. Go to https://makersuite.google.com/app/apikey and create the API Key. Before testing the prompt itself, let&amp;#8217;s see the different LLM models offered by Google. import google.generativeai as genai from dotenv import load_dotenv if __name__ == '__main__': load_dotenv() genai.configure(api_key=api_key) models = genai.list_models() for model in models: print(model.display_name, model.name) When you run this code it outputs: PaLM 2 Chat (Legacy) models/chat-bison-001 PaLM 2 (Legacy) models/text-bison-001 Embedding Gecko models/embedding-gecko-001 Gemini Pro models/gemini-pro Gemini Pro Vision models/gemini-pro-vision Embedding 001 models/embedding-001 Model that performs Attributed Question Answering. models/aqa For this post, I am going to use Gemini Pro. Now let&amp;#8217;s write the code using the prompt which will return the blog article in JSON format. import json import google.generativeai as genai from dotenv import load_dotenv import os from datetime import datetime def generate_article(stock_date): # Convert the string to a datetime object date_object = datetime.strptime(stock_date, "%Y-%m-%d") # Format the date formatted_date = date_object.strftime("%d %B %Y") load_dotenv() api_key = os.environ.get("GEMINI_API_KEY") prompt = f'Write me an article on stock market performance for the date {formatted_date}, including key indices such as the S&amp;#38;P 500, Dow Jones Industrial Average, and NASDAQ Composite. Highlight notable gainers and losers, identifying the sectors driving market movement. Discuss any significant economic events, corporate announcements, or geopolitical factors influencing market trends. Include information on trading volume, volatility, and any technical patterns observed. Additionally, comment on the overall market sentiment, investor behavior, and potential implications for the upcoming trading sessions. Lastly, offer insights into relevant commodities, currencies, and interest rates that may have impacted the broader financial landscape. Also suggest relevant Blog post title in H1 tag. At the end return data in JSON format with two fields: Title and Description where the "Title" contains the blog title and "Desrcription" contains the entire article. Do not return any other text besides JSON' genai.configure(api_key=api_key) model = genai.GenerativeModel("gemini-pro") response = model.generate_content(contents=prompt) response_text = response.text.replace("```json", '').replace("```", "") json_data = json.loads(response_text) return json_data if __name__ == '__main__': # Assuming you have a date string date_string = "2023-12-22" article = generate_article(date_string) print(article) Since I am writing the article on Sunday(24 December) and the market is closed, I have manually set the last Friday date here. Otherwise, the script will be working as a CRON job after market hours in working days and return the required data. As expected, when you run it, it generates the following JSON payload. I also had to sanitize the return output because the API often adds &amp;#8220;`json and &amp;#8220;` before and after the JSON payload. Anyways, we have done half of our work. Now we have to send this generated article to our WordPress blog. Download the latest version of WordPress from the website and install it on your machine. To enable the Markdown, you will have to install the Markup Markdown plugin. This plugin will disable the default editor and enable the Markdown editor. Cool, No? WordPress REST APIs Now let&amp;#8217;s test whether WordPress APIs are working or not. Before doing that, you have to change the format of permalink. The default format will not let you return the JSON data. I did something like the below, without doing this APIs will not work. Now let&amp;#8217;s make a sample call to http://localhost:8000/wp-json and if all goes well you will see something like the below: It returns a big JSON payload with all the details. The return of the JSON tells that the WordPress JSON API is working properly. Using API to create posts Before creating or updating/deleting a post you must be authenticated as a WordPress user. WordPress provides a Basic Auth plugin to do this. Download the file and put it in the plugins directory. Below is the code to create a post. You can check the docs to learn more about the endpoint related to post creation. def create_post(title, text): article_url = '' headers = { 'Content-Type': 'application/json', } json_data = { 'title': title, 'content': text, 'status': 'publish', } if title != '' and text != '': r = requests.post('http://localhost:8000/wp-json/wp/v2/posts', headers=headers, json=json_data, auth=('admin', 'admin')) if r.status_code == 201: article_url = r.json()['guid']['rendered'] return article_url Notice that I am passing my user/password, in this case, admin/admin for authentication purposes. For this very reason, I installed the authentication plugin. If all goes well, it will return the following output: Want to see how it got rendered? here you go! Sweet, isn&amp;#8217;t it? Conclusion So&amp;#8230; in this post, you learned how you can leverage the power of LLMs to automate a blog in a certain niche. In our case, a finance blog. Currently, Gemini APIs have very limited access to the Internet but one does not always need real-time data. You can modify this prompt further to return high-volume keywords and then set them as tags by using WordPress tags APIs. Like always, the code is available on GitHub. Do you need to automate blogs using chatGPT or Google Bard APIs for your desired niche? let me know and I will create a system for you. Contact at kadnan@gmail.com If you like this post then you should subscribe to my blog for future updates. * indicates required Email Address *&lt;/p&gt;
The post &lt;a href="https://blog.adnansiddiqi.me/automate-your-finance-blog-with-wordpress-and-google-bard-in-python/"&gt;Automate Your Finance Blog with WordPress and Google Bard in Python&lt;/a&gt; first appeared on &lt;a href="https://blog.adnansiddiqi.me"&gt;Adnan's Random bytes&lt;/a&gt;.</description><author>Adnan's Random bytes</author><pubDate>Sun, 24 Dec 2023 13:19:22 GMT</pubDate><guid isPermaLink="true">https://blog.adnansiddiqi.me/automate-your-finance-blog-with-wordpress-and-google-bard-in-python/</guid></item><item><title>Does Betteridge's Law apply to this headline?</title><link>https://taylor.town/is-betteridges-law</link><description>🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸</description><author>taylor.town</author><pubDate>Sun, 24 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/is-betteridges-law</guid></item><item><title>2023: Focusing on a single product pays off</title><link>https://maxrozen.com/2023-focus-single-product-pays-off</link><description>The first year where I managed to keep my focus entirely on a single project.</description><author>Max Rozen</author><pubDate>Sat, 23 Dec 2023 07:52:00 GMT</pubDate><guid isPermaLink="true">https://maxrozen.com/2023-focus-single-product-pays-off</guid></item><item><title>The pharma industry from Paul Janssen to today: why drugs got harder to develop and what we can do about it</title><link>https://atelfo.github.io/2023/12/23/biopharma-from-janssen-to-today.html</link><description/><author>Alex’s blog</author><pubDate>Sat, 23 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://atelfo.github.io/2023/12/23/biopharma-from-janssen-to-today.html</guid></item><item><title>How command line arguments get parsed</title><link>https://omarabid.com/command-arguments/</link><description>Explore how command line arguments are parsed.</description><author>Omar Abid - Personal Blog</author><pubDate>Sat, 23 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://omarabid.com/command-arguments/</guid></item><item><title>Home WiFi Upgrades: Adding an Access Point with Wired Backhaul</title><link>https://www.mikekasberg.com/blog/2023/12/22/home-wifi-upgrades-adding-an-access-point-with-wired-backhaul.html</link><description>&lt;p&gt;When we bought our house several years ago, our home wifi consisted of a cable
modem and wireless router on the floor of our living room (where we happened to
have a coax outlet). Over time, I’ve made a few improvements! Last year, I
&lt;a href="https://www.mikekasberg.com/blog/2022/12/22/i-installed-my-own-coax-cable.html"&gt;installed my own coax cable&lt;/a&gt; so I could move our internet
equipment out of the living room. This was a great improvement overall, but led
to bad coverage and slow speeds on my deck. To try to fix that, I got an
&lt;a href="https://www.tp-link.com/us/home-networking/range-extender/re450/"&gt;RE-450&lt;/a&gt; wifi
range extender. The range extender improved the wifi on the deck, but didn’t
work seamlessly with the rest of my network. So I planned some more
improvements. I just finished installing some Cat-6 ethernet cable so I could
add a wifi access point with a wired backhaul. I want to write this blog about
how I did it, what kind of performance I got, and what I learned along the way.
Maybe you can try something similar if you have wifi coverage problems in your
own home!&lt;/p&gt;

&lt;h2 id="wifi-problems"&gt;WiFi Problems&lt;/h2&gt;

&lt;p&gt;To get a thorough understanding of the problems I was trying to solve, I think
it’s important to understand a little about the layout of my house. We have a
mostly-finished basement with an unfinished 10’x10’ mechanical room (with our
furnace, water heater, and storage). Last year, I &lt;a href="https://www.mikekasberg.com/blog/2022/12/22/i-installed-my-own-coax-cable.html"&gt;moved my modem and wifi
router&lt;/a&gt; to this
mechanical room, right next to my basement office. Our main level has a kitchen
and a TV room, and the internet was great there. Our second story has only
bedrooms. The internet was just okay in the master bedroom. It worked, but it
was noticeably slower than other parts of the house. The other bedrooms were
worse, with barely-working wifi. Our deck had similarly bad wifi, with the
signal dropping intermittently.&lt;/p&gt;

&lt;p&gt;I tried to fix these problems several months ago with the range extender.
Perhaps it was just a problem with this particular model, but I was never able
to get the range extender working well. Whenever I’d set it up to use the same
SSID as my primary wifi network, clients would seem to get mixed up and
connections would drop. Eventually, I gave up on using the same SSID. The range
extender &lt;em&gt;did&lt;/em&gt; work on a separate SSID, and I used it this way to get wifi on my
deck. Unfortunately, though the range extender improved the overall coverage
area, the speed was still mediocre, and I didn’t like the need to change wifi
networks on the deck.&lt;/p&gt;

&lt;p&gt;&lt;img alt="Installing Cat-6 cable through a wall" src="https://www.mikekasberg.com/images/access-point-with-wired-backhaul/wireless-backhaul.png" /&gt;&lt;/p&gt;

&lt;h2 id="planning-a-solution"&gt;Planning a Solution&lt;/h2&gt;

&lt;p&gt;I suspected the reason I was experiencing dropped connections had to do with all
the traffic between my computer, the range extender (with wireless backhaul),
and my primary router. I also knew that (at least in theory) it was possible to
set up a seamless wireless network with multiple access points, where clients
would automatically choose the best access point – this is how corporate
networks have great reception throughout a huge office building. A major
difference between my range extender and those corporate networks was the
“backhaul” (connection back to the primary router). My range extender used a
wireless backhaul (because homeowners love the simplicity of just plugging in a
device to fix their problems, even if it doesn’t work that well), but
professional networks use wired backhaul for better performance and reliability.
As far as I know, it’s very uncommon for a “professional” wifi network to
use a wireless “mesh” or range extenders – it just isn’t cost-effective or
practical compared to the simplicity of access points with wired backhaul.
I decided to try setting up an access point with wired backhaul on my home
network.&lt;/p&gt;

&lt;p&gt;&lt;img alt="Installing Cat-6 cable through a wall" src="https://www.mikekasberg.com/images/access-point-with-wired-backhaul/wired-backhaul.png" /&gt;&lt;/p&gt;

&lt;p&gt;Fortunately, my range extender (an
&lt;a href="https://www.tp-link.com/us/home-networking/range-extender/re450/"&gt;RE-450&lt;/a&gt;) can
also function in access point mode! All I needed to do was run an ethernet cable
to it! This is easier said than done. Our home isn’t wired with ethernet, and
with a mostly-finished basement we have very limited access to run cables
through the walls (since we can’t just fish them down through an unfinished
basement). I needed to figure out where I could put a wifi access point and run
ethernet to it. After mulling over the problem for several weeks, an idea
occurred to me to run ethernet &lt;strong&gt;through our garage&lt;/strong&gt;. Ethernet cable is low voltage,
so it can be exposed (and it wouldn’t bother me to have a wire along the wall of
my garage; we already had a couple others). My garage shares a small section of
wall with my basement mechanical room. My garage has an available power outlet
(which is necessary since my RE-450 isn’t PoE). And, luckily, my garage is
somewhat centrally located near the part of the house with bad coverage – under
my master bedroom and near the deck. I decided to run a Cat-6 ethernet cable
from the wifi router in my basement mechanical room to a wifi access point on
the far side of our garage!&lt;/p&gt;

&lt;p&gt;&lt;img alt="Installing Cat-6 cable through a wall" src="https://www.mikekasberg.com/images/access-point-with-wired-backhaul/installing-cable.jpg" /&gt;&lt;/p&gt;

&lt;h2 id="installing-cat-6-cable"&gt;Installing Cat-6 Cable&lt;/h2&gt;

&lt;p&gt;I had very limited experience running ethernet cable. (Though I did have an
“internship” fifteen years ago terminating patch panels.) But coming off a
success &lt;a href="https://www.mikekasberg.com/blog/2022/12/22/i-installed-my-own-coax-cable.html"&gt;installing my own coax cable&lt;/a&gt; last year, I decided to give it a
go. I opted for Cat-6 cable over Cat-5. For about 30% difference in
price, it made sense to me to be more future-proof so I don’t need to rewire
this in five years. I went to Home Depot and bought my supplies:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://www.homedepot.com/p/Southwire-100-ft-Blue-23-4-Solid-CU-CAT6-CMR-Riser-Data-Cable-56918943/202316437"&gt;100 ft Cat-6 twisted pair
cable&lt;/a&gt; ($32)&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.homedepot.com/p/Klein-Tools-Twisted-Pair-Installation-Tool-Set-with-Zipper-Pouch-VDV026-212/304087142"&gt;Klein Tool
Set with crimper&lt;/a&gt;
($50)&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.homedepot.com/p/IDEAL-CAT-6-Single-Piece-Modular-Plug-25-Pack-85-367/308990482"&gt;RJ-45 Cat-6
plugs&lt;/a&gt; ($1/ea)&lt;/li&gt;
  &lt;li&gt;(Optional) &lt;a href="https://www.homedepot.com/p/Klein-Tools-LAN-Explorer-Data-Cable-Tester-with-Remote-VDV526-100/309925931"&gt;Klein LAN Explorer cable
tester&lt;/a&gt;
($30)&lt;/li&gt;
  &lt;li&gt;(Optional) &lt;a href="https://www.homedepot.com/p/Gardner-Bender-1-4-in-Polyethylene-Clip-On-Category-3-and-5-Data-Cable-Staple-Blue-100-Pack-PTP-100BX/315205659"&gt;Data cable staples&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I bought the Klein Tool set above, though I didn’t need the punch tool since
I wasn’t installing any jacks. I also opted for the traditional plugs rather than
pass-through connectors. Either are fine – just make sure your plugs match the
tool you use. At a fundamental level, all you need is Cat-6 cable, a plug for
each end, and a crimping tool to terminate the ends. Depending on your
particular needs, you might also want a drill (to route the cable through walls)
or data cable staples (to attach the cable to walls). I also bought a cable
tester to make sure I had a good connection to the plug with the ends in the
correct order, but this isn’t strictly necessary.&lt;/p&gt;

&lt;p&gt;&lt;img alt="Installing Cat-6 cable through a wall" src="https://www.mikekasberg.com/images/access-point-with-wired-backhaul/terminating.jpg" /&gt;&lt;/p&gt;

&lt;p&gt;Terminating ethernet cable with a crimping tool might seem intimidating if
you’ve never done it before, but it was actually one of the easier parts of this
project! Terminating cable is nothing compared to figuring out how to route it
through walls to the right spot. Just watch a five-minute YouTube video and make
sure you put the wires in the correct order. If I figured this out, you can too!&lt;/p&gt;

&lt;h2 id="results-and-lessons"&gt;Results and Lessons&lt;/h2&gt;

&lt;p&gt;&lt;img alt="Installing Cat-6 cable through a wall" src="https://www.mikekasberg.com/images/access-point-with-wired-backhaul/access-point.jpg" /&gt;&lt;/p&gt;

&lt;p&gt;I &lt;a href="https://www.mikekasberg.com/blog/2023/07/28/how-to-test-and-optimize-your-home-wifi.html"&gt;tested&lt;/a&gt;
the changes I made to my wifi network by running this SSH command (to time the
transfer of a 1GB file) from various points around my house before and after the
changes.&lt;/p&gt;

&lt;div class="language-plaintext highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;$ ssh 192.168.0.1 'dd if=/dev/zero bs=1GB count=1 2&amp;gt;/dev/null' | dd of=/dev/null status=progress
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;My results are summarized in the table below. “Router” represents the wifi
router in my basement (without the range extender). “Wired AP” is my new wifi
access point with wired backhaul. And the deltas compare the new setup to the
previous setups.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style="text-align: left;"&gt;Client Location&lt;/th&gt;
      &lt;th style="text-align: right;"&gt;Router&lt;/th&gt;
      &lt;th style="text-align: right;"&gt;Range Extender&lt;/th&gt;
      &lt;th style="text-align: right;"&gt;Wired AP&lt;/th&gt;
      &lt;th style="text-align: right;"&gt;Δ (vs Router)&lt;/th&gt;
      &lt;th style="text-align: right;"&gt;Δ (vs Extender)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style="text-align: left;"&gt;Basement Office&lt;/td&gt;
      &lt;td style="text-align: right;"&gt;48 MB/s&lt;/td&gt;
      &lt;td style="text-align: right;"&gt;N/A&lt;/td&gt;
      &lt;td style="text-align: right;"&gt;48 MB/s&lt;/td&gt;
      &lt;td style="text-align: right;"&gt;1x&lt;/td&gt;
      &lt;td style="text-align: right;"&gt;N/A&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style="text-align: left;"&gt;Master Bedroom&lt;/td&gt;
      &lt;td style="text-align: right;"&gt;28 MB/s&lt;/td&gt;
      &lt;td style="text-align: right;"&gt;27 MB/s&lt;/td&gt;
      &lt;td style="text-align: right;"&gt;55 MB/s&lt;/td&gt;
      &lt;td style="text-align: right;"&gt;1.9x&lt;/td&gt;
      &lt;td style="text-align: right;"&gt;2x&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style="text-align: left;"&gt;Far Bedroom&lt;/td&gt;
      &lt;td style="text-align: right;"&gt;14* MB/s&lt;/td&gt;
      &lt;td style="text-align: right;"&gt;25 MB/s&lt;/td&gt;
      &lt;td style="text-align: right;"&gt;46 MB/s&lt;/td&gt;
      &lt;td style="text-align: right;"&gt;3.3x&lt;/td&gt;
      &lt;td style="text-align: right;"&gt;1.8x&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style="text-align: left;"&gt;Deck&lt;/td&gt;
      &lt;td style="text-align: right;"&gt;2* MB/s&lt;/td&gt;
      &lt;td style="text-align: right;"&gt;19 MB/s&lt;/td&gt;
      &lt;td style="text-align: right;"&gt;38 MB/s&lt;/td&gt;
      &lt;td style="text-align: right;"&gt;19x&lt;/td&gt;
      &lt;td style="text-align: right;"&gt;2x&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p class="text-sm"&gt;*Connection unstable&lt;/p&gt;

&lt;p&gt;I couple things jump out at me. It’s worth noting that while the range extender
improved the connection in areas where it was previously unstable, it didn’t
really make the connection any faster where the connection was already stable
(in the master bedroom). But the same RE-450 in access point mode is almost
twice as fast across the board to any area it can reach. (It also works great,
without dropping connections, on the same SSID is my primary wifi router. Yay!)&lt;/p&gt;

&lt;p&gt;I learned several things from this experience, and I think some of the lessons I
learned will apply to anyone thinking about how to improve their home wifi.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;A range extender (without a wired backhaul) is a simple solution that &lt;em&gt;can&lt;/em&gt;
work to make wifi available where it wasn’t before. For example, wifi didn’t
work reliably on my deck without a range extender but did work with one. (I
needed to use a separate SSID; YMMV depending on your equipment.)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;An access point with a wired backhaul is almost always better than a wireless
range extender. A wired AP doesn’t “waste” half its wireless bandwidth
communicating with the primary wireless router.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;My deck and master bedroom are roughly the same distance from my basement
wifi router. But the master bedroom achieved 28 MB/s while the deck only got
about 2 MB/s on the router alone. I think a big factor here is interference.
There are only a couple walls and floors obstructing the signal to the bedroom,
but the signal to the deck is obstructed (partially) by the ground and a
concrete foundation. So when planning your wifi, you might need access points
closer together than you think if there’s a lot of obstruction.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;It’s worth knowing that while my ranger extender requires a power outlet even
in access point mode, this isn’t always necessary. If you don’t have a power
outlet available, some wireless access points can work via power over ethernet
(PoE). If you use this, your router needs to provide PoE power, or you can
inject power near the router.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If you ever have the opportunity to install ethernet wiring in the walls of
your house (either as a new build or during remodeling), &lt;strong&gt;do it!&lt;/strong&gt; Even if
most devices are wireless, the ability to put a wired access point in various
rooms would be a huge improvement to your network’s performance!&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re not satisfied with your home wifi speed and coverage, I think a wired
access point should be high on your list of improvements to consider. While it
might not be feasible in every situation, it can provide very good speed and
coverage if there’s a way to run an ethernet cable back to the router. And if
you have enough DIY chops to install the cable yourself, the wired access point
can be very cost-effective!&lt;/p&gt;

&lt;h2 id="whats-next"&gt;What’s Next?&lt;/h2&gt;

&lt;p&gt;I’m pretty darn happy with my home wifi now – I have fast coverage everywhere,
including very fast wifi in my bedroom and on my deck. 40+ MB/s (megabytes per
second) is fast enough that my ISP is the limiting factor (at roughly 150 Mbps
– megabits per second, or ~18 MB/s) rather than my home wifi. Still, I’m sure
I’ll want to upgrade more in the future as new technology comes out. I’ve heard
great things about Ubiquity, and would love to upgrade my router and access
points to Ubiquity hardware one day!&lt;/p&gt;</description><author>Mike Kasberg's Blog</author><pubDate>Sat, 23 Dec 2023 00:00:00 GMT</pubDate><guid isPermaLink="true">https://www.mikekasberg.com/blog/2023/12/22/home-wifi-upgrades-adding-an-access-point-with-wired-backhaul.html</guid></item><item><title>The Firewall Guy</title><link>https://0xfe.blogspot.com/2023/12/the-firewall-guy.html</link><description>&lt;p&gt;About 20 years ago, I worked as an independent consultant at a major telecom. I wrote a program that did some stuff, and couldn't get it to communicate on the network.&lt;/p&gt;&lt;p&gt;Developers there didn't get root on a machine, and there was limited access to tooling, so I reached out to the sysadmin. A day later he came back to me and said, "everything here's good, check with the firewall guy."&lt;/p&gt;&lt;p&gt;The firewall guy. It took me a day to track down the firewall guy.&lt;/p&gt;&lt;p&gt;"Umm... Mr. Firewall guy, can you help me out with this problem?"&lt;/p&gt;&lt;p&gt;Firewall guy was kinda grumpy. He begrudgingly collected some information, begrudgingly looked at stuff. "Firewall's fine. Talk to the network guy."&lt;/p&gt;&lt;p&gt;Two days later, I found the network guy, who was nicer: "Network's good. Talk to the firewall guy."&lt;/p&gt;&lt;p&gt;"I did a couple of days ago. Firewall guy says everything's fine."&lt;/p&gt;&lt;p&gt;"Okay, then you're probably resolving to the wrong addresses. Talk to the DNS guy."&lt;/p&gt;&lt;p&gt;It took me many days to find the DNS guy. DNS guy left six months ago. Firewall guy was now also DNS guy.&lt;/p&gt;&lt;p&gt;I did not like Firewall guy, but I didn't have a choice: "So Mr. Firewall guy, Network guy says it might be a DNS issue."&lt;/p&gt;&lt;p&gt;"You again. It definitely not a Firewall or DNS problem."&lt;/p&gt;&lt;p&gt;At this point I was kinda stuck. I was accountable for delivering something that works, but had no agency to actually deliver the thing.&lt;/p&gt;&lt;p&gt;As I was thinking about what to do next, Firewall guy called me on my desk phone: "try your thing again." I tried my thing again. "Thanks very much, that worked! What was wrong?"&lt;/p&gt;&lt;p&gt;"It's complicated. &amp;lt;hangs up&amp;gt;" I took a peek and saw the DNS entries didn't change. It was probably the firewall.&lt;/p&gt;&lt;p&gt;It took me about two days to build the thing, and eight days to navigate their bureaucracy. I billed them for ten days of my time, and apologized for taking so long.&lt;/p&gt;&lt;p&gt;The manager replied back right away: "Oh that was quick! No need to apologize. Our regular consulting firm estimated 2 people 3 months. Can you come by next week for your next project?"&lt;/p&gt;</description><author>0xFE - 11111110b - 0376</author><pubDate>Fri, 22 Dec 2023 20:33:16 GMT</pubDate><guid isPermaLink="true">https://0xfe.blogspot.com/2023/12/the-firewall-guy.html</guid></item><item><title>Starting a Cloudflare Worker from scratch</title><link>https://maxrozen.com/starting-a-cloudflare-worker-from-scratch</link><description>We don't need no fancy tooling!</description><author>Max Rozen</author><pubDate>Fri, 22 Dec 2023 07:52:00 GMT</pubDate><guid isPermaLink="true">https://maxrozen.com/starting-a-cloudflare-worker-from-scratch</guid></item><item><title>Knowing Your Workflow Tools: Streamlining JSON Linting with ESLint, Flat Config, and VS Code</title><link>https://medium.com/full-stack-architecture/knowing-your-workflow-tools-streamlining-json-linting-with-eslint-flat-config-and-vs-code-65f7173e9cc8?source=rss----7ddf416a7f74---4</link><description>&lt;p&gt;Achieving linter harmony by configuring ESLint as your sole json linter and leveraging the latest flat config.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/630/1*k0JQBL2VknfUe3N8EPZMeQ.png" /&gt;&lt;figcaption&gt;Image created using stable diffusion.&lt;/figcaption&gt;&lt;/figure&gt;&lt;h3&gt;Introduction&lt;/h3&gt;&lt;p&gt;Dealing with multiple linters on the same file type can often lead to conflicts, especially with json files in Visual Studio Code. By default, these files are linted by tools that come pre-installed with VS Code. However, using &lt;a href="https://eslint.org/"&gt;ESLint&lt;/a&gt; for json files at the command line, without configuring Visual Studio Code to do the same, can result in linting inconsistencies.&lt;/p&gt;&lt;p&gt;This guide will show you how to configure Visual Studio Code to use ESLint as the sole linter for json files, ensuring a consistent and reliable linting process throughout your development environment.&lt;/p&gt;&lt;p&gt;ESLint is also introducing a new configuration file format called &lt;a href="https://eslint.org/docs/latest/use/configure/configuration-files-new"&gt;flat config&lt;/a&gt; which which is used to setup the &lt;a href="https://ota-meshi.github.io/eslint-plugin-jsonc/"&gt;eslint-plugin-jsonc&lt;/a&gt; plugin.&lt;/p&gt;&lt;p&gt;The &lt;a href="https://github.com/erichosick/eslint-json-flat-config-template"&gt;eslint-json-flat-config-template&lt;/a&gt; repository on GitHub includes a &lt;a href="https://github.com/erichosick/eslint-json-flat-config-template/blob/main/eslint-json-flat-config-template.sh"&gt;shell script&lt;/a&gt; that configures your node project to support linting of json files using ESLint.&lt;/p&gt;&lt;h3&gt;The Opinionated TLDR&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Everything described below can be setup by running a shell script created for you. &lt;strong&gt;WARNING&lt;/strong&gt;: You should review shell scripts before running them off the internet.&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;curl https://raw.githubusercontent.com/erichosick/eslint-json-flat-config-template/main/eslint-json-flat-config-template.sh \&lt;br /&gt;  | bash&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;Favor respecting intended file extension content over misusing file extensions; even when technically possible. For example,.js, .cjs are &lt;a href="https://nodejs.org/docs/latest/api/modules.html"&gt;CommonJS&lt;/a&gt; a .mjs files are used for &lt;a href="https://nodejs.org/docs/latest/api/esm.html"&gt;ECMAscript&lt;/a&gt;. However, (as of v12 of node), it’s possible to &lt;a href="https://nodejs.org/docs/latest/api/esm.html#enabling"&gt;enable&lt;/a&gt; javascript to interpret .js extension ES modules.&lt;/li&gt;&lt;li&gt;When setting up Eslint config files, use the appropriate file extension type: .mjs for ESLint configuration using ES modules and .cjs for ESLint Common JS.&lt;/li&gt;&lt;li&gt;Favor a single tool for linting a given file type over using multiple tools to lint the same file type.&lt;/li&gt;&lt;li&gt;JSON Files should always be linting in the editor. If they aren’t, pressing Command + Shift + U then selecting ESLint in the dropdown shows debug information which is super helpful when there are issues.&lt;/li&gt;&lt;li&gt;For json linting and parsing, we are using &lt;a href="https://ota-meshi.github.io/eslint-plugin-jsonc/"&gt;eslint-plugin-jsonc&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;To enable flat config at the command line use the ESLINT_USE_FLAT_CONFIG=true environment variable.&lt;/li&gt;&lt;li&gt;To enable flat config in VS Code, use the .vscode/settings property &amp;quot;eslint.experimental.useFlatConfig”:true.&lt;/li&gt;&lt;li&gt;Don’t enable comments in json files. I want to. You want to. Use jsonc, yaml, json5 or a similar extension. Work within the constraints of the tech stacks you choose. In in javascript/node tech stack json files can’t have comments.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Introduction&lt;/h3&gt;&lt;p&gt;Getting ESLint to work consistently in both the command line and within the Visual Studio Code editor for jsonfiles felt unnecessarily complex. Even while writing this, I would continuously run into errors. Hopefully, this blog post, along with the shell script, helps you get flat file config ESLinting of json working as quickly as possible for you.&lt;/p&gt;&lt;h3&gt;Setup&lt;/h3&gt;&lt;h4&gt;1) Install Package Dependencies&lt;/h4&gt;&lt;p&gt;We are using eslint-plugin-jsonc to do linting of json , jsonc and json5 files.&lt;/p&gt;&lt;pre&gt;pnpm install --save-dev eslint eslint-plugin-jsonc&lt;/pre&gt;&lt;h4&gt;2) Configure ESLint in .vscode/settings.json&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;Set the environment to use eslint.config.mjs as the configuration file (overrideConfigFile).&lt;/li&gt;&lt;li&gt;Set the environment to use flat file configs (eslint.experimental.useFlatConfig ).&lt;/li&gt;&lt;li&gt;Enable ESlint to do json validation: disabling VS Codes, auto-installed, json linter (json.validate.enable).&lt;/li&gt;&lt;li&gt;By default, ESLint will not lint json files. Define which files ESLint will validate (eslint.validate). Note: eslint.options.extensions has been removed. Contrary to some documentations, this is required.&lt;/li&gt;&lt;li&gt;Enforce ESLint as the linter by setting defaults for json extensions (editor.default).&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;{&lt;br /&gt;  /// enable flat config files (eslint.config.mjs)&lt;br /&gt;  &amp;quot;eslint.experimental.useFlatConfig&amp;quot;: true,&lt;br /&gt;  /// Enables us to use the extended configuration&lt;br /&gt;  /// see: https://eslint.org/docs/latest/integrate/nodejs-api#-new-eslintoptions&lt;br /&gt;  &amp;quot;eslint.options&amp;quot;: {&lt;br /&gt;    &amp;quot;overrideConfigFile&amp;quot;: &amp;quot;eslint.config.mjs&amp;quot;,&lt;br /&gt;  },&lt;br /&gt;  /// Disable validation of JSON files using VS Code's built in JSON validator&lt;br /&gt;  /// and use eslint instead&lt;br /&gt;  /// see https://code.visualstudio.com/Docs/languages/json#_intellisense-and-validation&lt;br /&gt;  &amp;quot;json.validate.enable&amp;quot;: false,&lt;br /&gt;  /// Configure eslint to validate the following file types.&lt;br /&gt;  /// eslint.validate Default: .js, .mjs and .cjs (for non-flat it is only .js)&lt;br /&gt;  &amp;quot;eslint.validate&amp;quot;: [&lt;br /&gt;    &amp;quot;json&amp;quot;,&lt;br /&gt;    &amp;quot;jsonc&amp;quot;, /// also lints json5 files&lt;br /&gt;  ],&lt;br /&gt;  /// Assure other formatters are never used by setting eslint as the default&lt;br /&gt;  /// see https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint&lt;br /&gt;  &amp;quot;[json]&amp;quot;: {&lt;br /&gt;    &amp;quot;editor.defaultFormatter&amp;quot;: &amp;quot;dbaeumer.vscode-eslint&amp;quot;,&lt;br /&gt;  },&lt;br /&gt;  &amp;quot;[jsonc]&amp;quot;: {&lt;br /&gt;    &amp;quot;editor.defaultFormatter&amp;quot;: &amp;quot;dbaeumer.vscode-eslint&amp;quot;,&lt;br /&gt;  },&lt;br /&gt;}&lt;/pre&gt;&lt;h4&gt;3) Setup ESLint’s Flat Config&lt;/h4&gt;&lt;p&gt;Create a file named eslint.config.mjs with the following example configuration for linting json.&lt;/p&gt;&lt;pre&gt;/// An example configuration. Please see the github repository for a&lt;br /&gt;/// more complete configuration.&lt;br /&gt;import jsonc from 'eslint-plugin-jsonc';&lt;br /&gt;&lt;br /&gt;const eslintConfigObjects = [&lt;br /&gt;  {&lt;br /&gt;    files: ['**/*.json', '**/*.jsonc', '**/*.json5'],&lt;br /&gt;    plugins: {&lt;br /&gt;      jsonc,&lt;br /&gt;    },&lt;br /&gt;    languageOptions: {&lt;br /&gt;      parser: jsonc,&lt;br /&gt;    },&lt;br /&gt;    rules: {&lt;br /&gt;      ...jsonc.configs['all'].rules,&lt;br /&gt;      /// jsonc and json5 can have comments&lt;br /&gt;      'jsonc/no-comments': 'off',&lt;br /&gt;      'jsonc/auto': 'off',&lt;br /&gt;      /// Indent with 2 spaces&lt;br /&gt;      'jsonc/indent': ['error', 2, {}],&lt;br /&gt;      /// Support arrays with and without new lines but they can't mix&lt;br /&gt;      'jsonc/array-element-newline': ['error', 'consistent'],&lt;br /&gt;      /// Don't sort keys. We want to keep the order of keys as they are:&lt;br /&gt;      /// especially in arrays.&lt;br /&gt;      'jsonc/sort-keys': 'off',&lt;br /&gt;      /// Not all keys are camelCase&lt;br /&gt;      'jsonc/key-name-casing': 'off',&lt;br /&gt;      /// see https://ota-meshi.github.io/eslint-plugin-jsonc/rules/comma-dangle.html&lt;br /&gt;      'jsonc/comma-dangle': ['error', 'always-multiline'],&lt;br /&gt;    },&lt;br /&gt;  },&lt;br /&gt;  {&lt;br /&gt;    files: ['**/*.json'],&lt;br /&gt;    plugins: {&lt;br /&gt;      jsonc,&lt;br /&gt;    },&lt;br /&gt;    languageOptions: {&lt;br /&gt;      parser: jsonc,&lt;br /&gt;    },&lt;br /&gt;    rules: {&lt;br /&gt;      /// Overrides the above configuration object.&lt;br /&gt;      /// No matter how much it would be cool to have comments in json files,&lt;br /&gt;      /// it's not supported by the json spec. We get errors tools like jq and&lt;br /&gt;      /// node.&lt;br /&gt;      'jsonc/no-comments': 'error'&lt;br /&gt;    },&lt;br /&gt;];&lt;br /&gt;&lt;br /&gt;export default eslintConfigObjects;&lt;/pre&gt;&lt;h4&gt;4) Running ESLint on the Command Line and in package.json&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;Enable flat file config (ESLINT_USE_FLAT_CONFIG)&lt;/li&gt;&lt;li&gt;Get ESLint to use a .mjs extension --config eslint.config.mjs. ESLint flat config, at this time, can’t automatically find the eslint.config.mjs file extension. So, we need to explicitly tell ESLint where the config file is.&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;{&lt;br /&gt;  /// ...&lt;br /&gt;  &amp;quot;scripts&amp;quot;: {&lt;br /&gt;    &amp;quot;eslint&amp;quot;: &amp;quot;ESLINT_USE_FLAT_CONFIG=true npx eslint --config eslint.config.mjs .&amp;quot;&lt;br /&gt;  },&lt;br /&gt;  /// ...&lt;br /&gt;}&lt;/pre&gt;&lt;p&gt;If everything is setup correctly, then the setup should start linting the .vscode/settings.json file. Try making an invalid change to the config file and you should see an error:&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qF_HEvtASXpWgVU1hIKQvw.png" /&gt;&lt;/figure&gt;&lt;h3&gt;Debugging ESLint in VS Code&lt;/h3&gt;&lt;h4&gt;1) ESLint Extension Installed&lt;/h4&gt;&lt;p&gt;Is there an eslint extension installed (&lt;a href="https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint"&gt;ESLint extension&lt;/a&gt; by Microsoft)?&lt;/p&gt;&lt;h4&gt;2) ESLint Output Log&lt;/h4&gt;&lt;p&gt;Files won’t lint if there are issues with the config file itself. Pressing Command + Shift + U then selecting ESLint in the dropdown shows debug information which is super helpful when there are issues with the config file itself.&lt;/p&gt;&lt;h4&gt;3) Are Changes in Rules Being Applied?&lt;/h4&gt;&lt;p&gt;Changing the config file should have an immediate effect on open files. In cases where you aren’t seeing expected linter issues, try:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;reloading VS Code using Command + Shift + P then searching for Reload Window. &lt;strong&gt;WARNING: reload window looses edit history.&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;restarting VS Code.&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;4) Disabling Intellisense Validation of JSON&lt;/h4&gt;&lt;p&gt;Visual Studio Code’s intellisense is setup to validate JSON automatically. Try reviewing the Setup above.&lt;/p&gt;&lt;h3&gt;Error Messages and Potential Fixes&lt;/h3&gt;&lt;p&gt;You may see the following errors in the output tab for ESLint (in VS Code press Command + Shift + U then select ESLint from the drop down).&lt;/p&gt;&lt;h4&gt;“No ESLint configuration found in…”&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;Do you have an eslint.config.mjs file at the root of the project?&lt;/li&gt;&lt;li&gt;Did you override the config file name in .vscode/settings.json ?&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;/// .vscode/settings.json&lt;br /&gt;{&lt;br /&gt;  /// Enables us to use the extended configuration&lt;br /&gt;  /// see: https://eslint.org/docs/latest/integrate/nodejs-api#-new-eslintoptions&lt;br /&gt;  &amp;quot;eslint.options&amp;quot;: {&lt;br /&gt;    &amp;quot;overrideConfigFile&amp;quot;: &amp;quot;eslint.config.mjs&amp;quot;&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;&lt;h4&gt;“YAMLException: Cannot read config file: /…/eslint.config.mjs”&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;ESLint flag config (at the time this was written) is experimental. Did you set the experimental flag?&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;/// .vscode/settings.json&lt;br /&gt;{&lt;br /&gt;  /// enables using newer flat eslint files: eslint.config.mjs&lt;br /&gt;  &amp;quot;eslint.experimental.useFlatConfig&amp;quot;: true,&lt;br /&gt;}&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;For the command line, did you use the ESLINT_USE_FLAT_CONFIG option?&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;ESLINT_USE_FLAT_CONFIG=true npx eslint --config eslint.config.mjs .&lt;/pre&gt;&lt;h4&gt;“Request textDocument/formatting failed with message: Cannot read properties of undefined (reading ‘ignores’)”&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;Do you have the minimal required configuration for ESLint flat file?&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;/// eslint.config.mjs (minimal)&lt;br /&gt;const eslintConfigObjects = [];&lt;br /&gt;&lt;br /&gt;export default eslintConfigObjects;&lt;/pre&gt;&lt;h4&gt;“Request eslint/noLibrary failed with message: command ‘npm.packageManager’ not found”&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;This is most likely caused by an invalid package.json file or even just a file named package.json (also invalid) that is in the same directory or a parent directory as the file being linted.&lt;/li&gt;&lt;/ul&gt;&lt;img alt="" height="1" src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=65f7173e9cc8" width="1" /&gt;&lt;hr /&gt;&lt;p&gt;&lt;a href="https://medium.com/full-stack-architecture/knowing-your-workflow-tools-streamlining-json-linting-with-eslint-flat-config-and-vs-code-65f7173e9cc8"&gt;Knowing Your Workflow Tools: Streamlining JSON Linting with ESLint, Flat Config, and VS Code&lt;/a&gt; was originally published in &lt;a href="https://medium.com/full-stack-architecture"&gt;The Opinionated Software Architect&lt;/a&gt; on Medium, where people are continuing the conversation by highlighting and responding to this story.&lt;/p&gt;</description><author>The Opinionated Software Architect - Medium</author><pubDate>Fri, 22 Dec 2023 03:50:16 GMT</pubDate><guid isPermaLink="true">https://medium.com/full-stack-architecture/knowing-your-workflow-tools-streamlining-json-linting-with-eslint-flat-config-and-vs-code-65f7173e9cc8?source=rss----7ddf416a7f74---4</guid></item><item><title>A Murder at the End of the World: Are you Vi or Emacs?</title><link>https://xenodium.com/are-you-vi-or-emacs</link><description>&lt;p&gt;I've enjoyed watching &lt;a href="https://www.imdb.com/title/tt15227418/"&gt;A Murder at the End of the World&lt;/a&gt;. The show may resonate with folks following the tech world. Won't say much more than that…&lt;/p&gt;
&lt;p&gt;What I can maybe say is, the shows features &lt;a href="https://www.reddit.com/r/emacs/"&gt;Reddit&lt;/a&gt;, &lt;a href="https://brave.com/"&gt;Brave browser&lt;/a&gt;, terminal usage (ifconfig, nmap, hydra, responder), and a reference to the good 'ol &lt;a href="https://en.wikipedia.org/wiki/Editor_war"&gt;Vi vs Emacs rivalry&lt;/a&gt;, which I hope folks these days don't take further than friendly teasing between dear cousins.&lt;/p&gt;
&lt;p&gt;In any case, being an Emacs nut, the scene gave me a good tickle. It's a great show, with a lovely Emacs cherry on top! While the show title and description didn't immediately draw me in, I'm glad I gave it a chance.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/are-you-vi-or-emacs/vi-or-emacs.webp" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/are-you-vi-or-emacs/what-is-emacs.webp" /&gt;&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Fri, 22 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/are-you-vi-or-emacs</guid></item><item><title>A Frivolous Feature</title><link>https://www.marginalia.nu/log/96_frivolous_asn/</link><description>&lt;p&gt;Marginalia Search very recently gained the ability to filter results by Autonomous System,
not only searching by ASN but by the organization information for that AS. At a glance this
seems like a somewhat frivolous feature, but it has interesting effects.&lt;/p&gt;
&lt;p&gt;Autonomous Systems are part of the Internet&amp;rsquo;s routing infrastructure. If your mental model of an IP
number is that they are the phone number of the computer, this is something akin to a postal code.
Digging much deeper than that into BGP and autonomous systems is not really in the scope of this article, but
&lt;a href="https://en.wikipedia.org/wiki/Autonomous_system_%28Internet%29"&gt;Wikipedia&lt;/a&gt; has a relatively lucid article on this.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Fri, 22 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/96_frivolous_asn/</guid></item><item><title>Software engineering bookmarks</title><link>https://vit.baisa.cz/notes/code/bookmarks/</link><description>links to various interesting resources</description><author>Vít Baisa</author><pubDate>Fri, 22 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://vit.baisa.cz/notes/code/bookmarks/</guid></item><item><title>My reference was dropped, why is the compiler complaining about multiple borrows?</title><link>https://ntietz.com/blog/my-reference-was-dropped-why-is-the-compiler-complaining-about-multiple-borrows/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;Recently someone I was talking to ran into a fun borrow checker problem in Rust which is illustrative of some current underlying limitations of Rust's borrow checker.
The problem boiled down to: they took a reference in a loop (dropped on each iteration), and the borrow checker complains that it cannot borrow it mutably multiple times, since it was borrowed in a previous iteration.
But: didn't we drop it?
Why is it still borrowed?&lt;/p&gt;
&lt;p&gt;Here's an example, because one code example is worth a thousand words.
In this example, we define a function called &lt;code&gt;find_leading_0s&lt;/code&gt; which takes in a slice of bytes and returns the slice containing the prefix of leading 0s as a mutable reference&lt;sup class="footnote-reference" id="fr-it-has-a-bug-1"&gt;&lt;a href="https://ntietz.com/blog/my-reference-was-dropped-why-is-the-compiler-complaining-about-multiple-borrows/#fn-it-has-a-bug"&gt;[1]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #fa5c4b;"&gt;fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;main&lt;/span&gt;&lt;span&gt;() {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let mut&lt;/span&gt;&lt;span&gt; bytes &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;9&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;3&lt;/span&gt;&lt;span&gt;];
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; padding &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;find_leading_0s&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;mut&lt;/span&gt;&lt;span&gt; bytes);
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;println!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;padding: &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;{:?}&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;, padding);
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;find_leading_0s&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;bytes&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;mut&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;]) -&amp;gt; &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Option&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;mut &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;]&amp;gt; {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let mut&lt;/span&gt;&lt;span&gt; index &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;while&lt;/span&gt;&lt;span&gt; index &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; bytes.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;len&lt;/span&gt;&lt;span&gt;() {
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; padding &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &amp;amp;&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;mut&lt;/span&gt;&lt;span&gt; bytes[&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;..&lt;/span&gt;&lt;span&gt;index];
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;if&lt;/span&gt;&lt;span&gt; padding[index] &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;!= &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0 &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;return &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Some&lt;/span&gt;&lt;span&gt;(padding);
&lt;/span&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;        index &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;+= &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;1&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;None
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inside each iteration of the loop, we take a slice and if the next element is non-zero we return this slice.
Otherwise, we keep going.
If we get to the end and we've found no non-zero elements, we can return some default value.&lt;/p&gt;
&lt;p&gt;If you compile this, you'll get the following error:&lt;/p&gt;
&lt;pre style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code&gt;&lt;span&gt;&amp;gt; rustc borrow.rs
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;error[E0502]: cannot borrow `*bytes` as immutable because it is also borrowed as mutable
&lt;/span&gt;&lt;span&gt;  --&amp;gt; borrow.rs:10:19
&lt;/span&gt;&lt;span&gt;   |
&lt;/span&gt;&lt;span&gt;7  | fn find_leading_0s(bytes: &amp;amp;mut [u8]) -&amp;gt; Option&amp;lt;&amp;amp;mut [u8]&amp;gt; {
&lt;/span&gt;&lt;span&gt;   |                           - let's call the lifetime of this reference `'1`
&lt;/span&gt;&lt;span&gt;...
&lt;/span&gt;&lt;span&gt;10 |     while index &amp;lt; bytes.len() {
&lt;/span&gt;&lt;span&gt;   |                   ^^^^^^^^^^^ immutable borrow occurs here
&lt;/span&gt;&lt;span&gt;11 |         let padding = &amp;amp;mut bytes[..index];
&lt;/span&gt;&lt;span&gt;   |                            ----- mutable borrow occurs here
&lt;/span&gt;&lt;span&gt;12 |         if padding[index] == 0 {
&lt;/span&gt;&lt;span&gt;13 |             return Some(padding);
&lt;/span&gt;&lt;span&gt;   |                    ------------- returning this value requires that `*bytes` is borrowed for `'1`
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;error[E0499]: cannot borrow `*bytes` as mutable more than once at a time
&lt;/span&gt;&lt;span&gt;  --&amp;gt; borrow.rs:11:28
&lt;/span&gt;&lt;span&gt;   |
&lt;/span&gt;&lt;span&gt;7  | fn find_leading_0s(bytes: &amp;amp;mut [u8]) -&amp;gt; Option&amp;lt;&amp;amp;mut [u8]&amp;gt; {
&lt;/span&gt;&lt;span&gt;   |                           - let's call the lifetime of this reference `'1`
&lt;/span&gt;&lt;span&gt;...
&lt;/span&gt;&lt;span&gt;11 |         let padding = &amp;amp;mut bytes[..index];
&lt;/span&gt;&lt;span&gt;   |                            ^^^^^ `*bytes` was mutably borrowed here in the previous iteration of the loop
&lt;/span&gt;&lt;span&gt;12 |         if padding[index] == 0 {
&lt;/span&gt;&lt;span&gt;13 |             return Some(padding);
&lt;/span&gt;&lt;span&gt;   |                    ------------- returning this value requires that `*bytes` is borrowed for `'1`
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;error: aborting due to 2 previous errors
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;Some errors have detailed explanations: E0499, E0502.
&lt;/span&gt;&lt;span&gt;For more information about an error, try `rustc --explain E0499`.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key error is this:&lt;/p&gt;
&lt;pre style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code&gt;&lt;span&gt;`*bytes` was mutably borrowed here in the previous iteration of the loop
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The underlying code seems sound, though, because the reference drops and so you will never actually hold the mutable reference across loop iterations.
But the borrow checker is rejecting it.
Why's that?&lt;/p&gt;
&lt;h1 id="a-long-standing-issue"&gt;A long-standing issue&lt;/h1&gt;
&lt;p&gt;There are some longstanding issues on the Rust compiler which are related here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/rust-lang/rust/issues/54663"&gt;rust-lang/rust#54663: Borrow checker extends borrow range in code with early return&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rust-lang/rust/issues/70255"&gt;rust-lang-rust#70255: Weird error for mutable references in a loop&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These relate to the same issue with code samples of their own, some which are loops and some which are conditionals.
They all boil down to the same problem, which has been dubbed Polonius.
This is best summarized in the Rust blog post &lt;a href="https://blog.rust-lang.org/inside-rust/2023/10/06/polonius-update.html"&gt;Polonius update&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Lifetimes can be named or anonymous.
If lifetimes are in the signature of a function, either explicitly (&lt;code&gt;fn f&amp;lt;'a&amp;gt;(x: &amp;amp;'a str) ...&lt;/code&gt;) or implicitly, like here, then they're named.
Even though our lifetimes are inferred (see the &lt;a href="https://doc.rust-lang.org/reference/lifetime-elision.html"&gt;lifetime elision rules&lt;/a&gt;) they are part of the signature.&lt;/p&gt;
&lt;p&gt;The current way the borrow checker works, if a lifetime is named, then it is deemed to last until the end of the function across &lt;em&gt;all&lt;/em&gt; code paths&lt;sup class="footnote-reference" id="fr-polonius-the-crab-1"&gt;&lt;a href="https://ntietz.com/blog/my-reference-was-dropped-why-is-the-compiler-complaining-about-multiple-borrows/#fn-polonius-the-crab"&gt;[2]&lt;/a&gt;&lt;/sup&gt;.
So even if you have an early return whenever you grab that reference, or you drop it across iterations of the loop, it doesn't matter: it's still going to be treated as if it's held for the whole function!
&lt;em&gt;Why&lt;/em&gt; this is the case is a much deeper question that I don't have the expertise to answer, but the Polonius update blog post mentioned above goes into some more detail.&lt;/p&gt;
&lt;p&gt;This all is a bit of a downer since a lot of code could be made terser and more readable by allowing this kind of pattern.
Fortunately we can work around it, and we will eventually not have to.&lt;/p&gt;
&lt;h1 id="a-workaround"&gt;A workaround&lt;/h1&gt;
&lt;p&gt;The code example above can be rewritten like this:&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #fa5c4b;"&gt;fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;main&lt;/span&gt;&lt;span&gt;() {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let mut&lt;/span&gt;&lt;span&gt; bytes &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;9&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;3&lt;/span&gt;&lt;span&gt;];
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; padding &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;find_leading_0s&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;mut&lt;/span&gt;&lt;span&gt; bytes);
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;println!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;padding: &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;{:?}&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;, padding);
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;find_leading_0s&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;bytes&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;mut&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;]) -&amp;gt; &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Option&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;mut &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;]&amp;gt; {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let mut&lt;/span&gt;&lt;span&gt; index &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;while&lt;/span&gt;&lt;span&gt; index &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; bytes.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;len&lt;/span&gt;&lt;span&gt;() {
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;if&lt;/span&gt;&lt;span&gt; bytes[index] &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;!= &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0 &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;return &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Some&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;mut&lt;/span&gt;&lt;span&gt; bytes[&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;..&lt;/span&gt;&lt;span&gt;index]);
&lt;/span&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;        index &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;+= &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;1&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;None
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, instead of creating a new mut reference into the slice, we use the existing single mut reference across all iterations.
You can similarly hoist your references out of the loop and solve things that way.&lt;/p&gt;
&lt;p&gt;This example does compile and works as expected.
We have a few other workarounds available to us:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use the &lt;a href="https://docs.rs/polonius-the-crab/latest/polonius_the_crab/index.html"&gt;polonius-the-crab&lt;/a&gt; crate. By using a macro, your lifetimes end up anonymous instead of named and this issue goes away.&lt;/li&gt;
&lt;li&gt;Use dedicated APIs like &lt;code&gt;get_or_insert()&lt;/code&gt; that avoid this problem for us&lt;/li&gt;
&lt;li&gt;Re-order some of the code to avoid this particular problem in a clunky way&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This doesn't feel great, because we're limited in the code we can write.&lt;/p&gt;
&lt;h1 id="the-future"&gt;The future!&lt;/h1&gt;
&lt;p&gt;In a future release of the borrow checker, this should be resolved.
Per the &lt;a href="https://blog.rust-lang.org/inside-rust/2023/10/06/polonius-update.html"&gt;working group's update&lt;/a&gt; on this issue, the goal is to have the Polonius problem stable by Rust 2024.&lt;/p&gt;
&lt;p&gt;The solution they're working on changes some of the underlying machinery of the borrow checker and then tracks things across each point in the control flow graph, instead of once.
This will let us use this type of mutable borrow with early return, and make a lot of neat code both safe and compilable!&lt;/p&gt;
&lt;p&gt;This looks like quite a big project.
I know a lot of people have worked on it for a long time.
Hopefully this will land in 2024.
I'll be keeping an eye out for more updates on it, and I'm excited to see the ergonomic code we can write once this lands!&lt;/p&gt;
&lt;hr /&gt;
&lt;hr /&gt;&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-it-has-a-bug"&gt;
&lt;p&gt;There's a bug in this program: if we reach the end of the list without an early exit, we should return &lt;code&gt;Some(bytes)&lt;/code&gt; instead of &lt;code&gt;None&lt;/code&gt;. This is intentional to produce the "previous iteration of the loop" error, instead of a different error, but they follow the same idea. &lt;a href="https://ntietz.com/blog/my-reference-was-dropped-why-is-the-compiler-complaining-about-multiple-borrows/#fr-it-has-a-bug-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-polonius-the-crab"&gt;
&lt;p&gt;There's a library called &lt;a href="https://docs.rs/polonius-the-crab/latest/polonius_the_crab/index.html#rationale-limitations-of-the-nll-borrow-checker"&gt;polonius-the-crab&lt;/a&gt; which has docs that have a great explanation of this. Their explanation helped me write this post. &lt;a href="https://ntietz.com/blog/my-reference-was-dropped-why-is-the-compiler-complaining-about-multiple-borrows/#fr-polonius-the-crab-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Fri, 22 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/my-reference-was-dropped-why-is-the-compiler-complaining-about-multiple-borrows/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Tear Duct Enhancement Surgery</title><link>https://taylor.town/tear-duct-enhancement</link><description>Cry TWICE as hard.</description><author>taylor.town</author><pubDate>Fri, 22 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/tear-duct-enhancement</guid></item><item><title>The Next Data Point</title><link>https://murphyslab.ca/notes/next-data-point/</link><description>One simple experiment I have on the go has given me pause to reflect on the data — largely thanks to the time gap between finding the next point. But I&amp;rsquo;ve appreciated having an opportunity to stop and think about individual data points carefully. Not because the underlying data is that novel. Rather I&amp;rsquo;ve specifically considered how each of those data points affect my regression and the numeric estimates of the true relationship for the data.</description><author>Murphy's Lab Notes on Murphy's Lab</author><pubDate>Fri, 22 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://murphyslab.ca/notes/next-data-point/</guid></item><item><title>12 Languages in 12 Months</title><link>https://xavd.id/blog/post/12-languages-in-12-months/</link><description>I tried 12 different new (to me) programming languages during 2023 and I share my thoughts on the pros and cons of each.&lt;br /&gt;&lt;br /&gt;&lt;a href="https://xavd.id/blog/post/12-languages-in-12-months/"&gt;Read the whole thing&lt;/a&gt;.</description><author>The David Brownman Blog</author><pubDate>Fri, 22 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://xavd.id/blog/post/12-languages-in-12-months/</guid></item><item><title>Dropbox (and Kobo) are now supported by url2epub</title><link>https://b.yuxuan.org/url2epub-dropbox</link><description>&lt;p&gt;Over this past Black Friday shopping season, I purchased yet another e-ink reader: &lt;a href="https://us.kobobooks.com/products/kobo-sage"&gt;Kobo Sage&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I wrote &lt;a href="https://b.yuxuan.org/url2epub-kindle"&gt;before&lt;/a&gt; about my frustration with reMarkable Cloud API and switched from reMarkable 2 to my wife&amp;rsquo;s old Kindle Oasis, but there are still things with Kindle Oasis that annoy me, so combining the shopping season discounts and my employer&amp;rsquo;s benefits, and also Kobo operates a Taiwan ebook store that I can buy Traditional Chinese books directly, so I decided to give Kobo a try.&lt;/p&gt;

&lt;p&gt;Initially there&amp;rsquo;s another thing that attracts me to the Kobo ecosystem: that they have built-in integration with &lt;a href="https://getpocket.com"&gt;Pocket&lt;/a&gt;, so the hope was that I won&amp;rsquo;t even need my own &lt;a href="https://github.com/fishy/url2epub"&gt;url2epub&lt;/a&gt; app to read internet articles on it.&lt;/p&gt;

&lt;p&gt;But after trying to use Pocket on it for a while (including two trips that I had some long times on a plane), my conclusion is that url2epub is just much better than Pocket to generate articles out of URLs to read on an e-ink reader, for the following reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pocket struggles to get some of the images from the articles, like The Verge&amp;rsquo;s (If any Mozilla engineer is reading this: I already described how I resolve that problem in &lt;a href="https://b.yuxuan.org/lazy-loading-image-url2epub"&gt;this previous blog post&lt;/a&gt;, my code is also licensed under BSD-3-Clause, so please implement it in Pocket. It&amp;rsquo;s not that hard).&lt;/li&gt;
&lt;li&gt;Pocket also struggles to get some paywall&amp;rsquo;d articles completely (e.g. Washington Post), that in Pocket app it opens the original url, while on Kobo those articles won&amp;rsquo;t be able to be synced at all.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Besides Pocket, Kobo also has &lt;a href="https://drive.google.com/"&gt;Google Drive&lt;/a&gt; and &lt;a href="https://dropbox.com/"&gt;Dropbox&lt;/a&gt; integrations (according to some internet discussion those actually varies device by device, e.g. not all Kobo devices have those integrations, but there also &lt;a href="https://github.com/fsantini/KoboCloud"&gt;third-party ways&lt;/a&gt; to add those integrations to all Kobo devices). I usually use Google Drive, but Kobo&amp;rsquo;s official Google Drive integration requires permission to access my whole Google Drive, and there&amp;rsquo;s no way I&amp;rsquo;m giving any third party app that permission, so I opt&amp;rsquo;d for Dropbox integration instead (as a free Dropbox account should be big enough to hold my generated epub files anyways).&lt;/p&gt;

&lt;p&gt;In url2epub Telegram bot, you can just use &lt;code&gt;/start dropbox&lt;/code&gt; to start the integration, which will give you a Dropbox OAuth URL. After granting permissions, it will give you a code in the end, so you come back with &lt;code&gt;/start dropbox &amp;lt;code&amp;gt;&lt;/code&gt; to finish the authentication. After that, you can use &lt;code&gt;/dir&lt;/code&gt; command to choose which directory to send those epub files to. Kobo&amp;rsquo;s Dropbox integration is an app integration that they only have access to files under &lt;code&gt;/App/Rakuten Kobo/&lt;/code&gt;, so in order to use it with a Kobo e-ink reader you need to pick a directory under that (I personally use &lt;code&gt;/App/Rakuten Kobo/Incoming/&lt;/code&gt; for it).&lt;/p&gt;

&lt;p&gt;After implementing that one thing I noticed is that for some reason my Kobo Sage does not like the epub files generated by url2epub, that the file will just show a blank screen. I tried to use some online epub validator to see what are the problems with the epub files I generated, and fixed some minor issues, but that didn&amp;rsquo;t make Kobo happy. One thing I noticed is that the validators say my epub&amp;rsquo;s main html file lacks the XML namespace on the &lt;code&gt;html&lt;/code&gt; node, e.g. the spec expects something like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;html xmlns=&amp;quot;http://www.w3.org/1999/xhtml&amp;quot; ...&amp;gt;
...
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But the epubs I generated do not have that &lt;code&gt;xmlns&lt;/code&gt; tag.&lt;/p&gt;

&lt;p&gt;I use &lt;a href="https://pkg.go.dev/golang.org/x/net/html"&gt;go&amp;rsquo;s HTML tokenizer and parser library&lt;/a&gt; to strip down readable html and render it in epub. It does have a &lt;a href="https://pkg.go.dev/golang.org/x/net/html#Node.Namespace"&gt;&lt;code&gt;Namespace&lt;/code&gt; field in &lt;code&gt;Node&lt;/code&gt; type&lt;/a&gt; to set the namespace, but setting it to &lt;code&gt;&amp;quot;http://www.w3.org/1999/xhtml&amp;quot;&lt;/code&gt; does not cause it to be rendered (probably because that&amp;rsquo;s the default and always rendering it will be too noisy?). After some testing, I ended up &lt;a href="https://github.com/fishy/url2epub/commit/93841a3c60b12f024a2b280b0171432181d93742"&gt;adding an &lt;code&gt;xmlns&lt;/code&gt; attribute explicitly to the html node&lt;/a&gt;, which finally made it to render it in the final file, and Kobo is finally able to read those epub files.&lt;/p&gt;

&lt;p&gt;Here is a comparison between Kobo showing the epub from url2epub from &lt;a href="https://admiralcloudberg.medium.com/a-matter-of-millimeters-the-story-of-qantas-flight-32-bdaa62dc98e7"&gt;this article&lt;/a&gt;:&lt;/p&gt;

&lt;figure&gt;
  &lt;img alt="Article rendered on Kobo Sage from url2epub" src="https://lh3.googleusercontent.com/pw/ABLVV87pdpNBdHGhc02Q4PH6wA0STsSRgv8ip0ubTbqRcOtwCYwIgF6foZ2TTx1F74IxrSf9ZFLIg6GQKGB0vy2Lox41WpDRuGkbD1tu1lunFVVEBgYK51pwUuuRKoBSiRwSfMGCBRZd655E0X-8Y_4FOIdNIw=w720" /&gt;
  &lt;figcaption&gt;&lt;a href="https://photos.app.goo.gl/5GNuet7FCkK4Qvsn7"&gt;Article rendered on Kobo Sage from url2epub&lt;/a&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;versus:&lt;/p&gt;

&lt;figure&gt;
  &lt;img alt="Article rendered on Kobo Sage from Pocket" src="https://lh3.googleusercontent.com/pw/ABLVV85wKt3d_m6xzmtLXkSTVWc_J5dyVeret-A4vh79BEx2I4Ydk9ZxBnYJ-rihB1BCi5yTyOF-TESkzY06cV_gSNh-8GMB2xoSpq8amxCYNifIhoS1-cKl1vfVH2OLeB1LGeXTl_XA2RvMv41Jf35ykPjKdg=w720" /&gt;
  &lt;figcaption&gt;&lt;a href="https://photos.app.goo.gl/teWJzqqvLzDxPvdu9"&gt;Article rendered on Kobo Sage from Pocket&lt;/a&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;From which you can clearly see the importance of the (lack of) image right above the image caption (&amp;ldquo;The trajectories of the various fragments. (ATSB)&amp;rdquo;).&lt;/p&gt;

&lt;p&gt;So if you also have a Kobo e-ink reader and like to read long internet articles on it, you can give it a try. There are probably other e-ink reader platforms with Dropbox integrations as well, for example, reMarkable itself also has a Dropbox integration, so this can be used beyond Kobo e-ink readers.&lt;/p&gt;

&lt;p&gt;Please note that at the time of writing, my Dropbox app was not approved as production yet, so it has a limit that it can only auth 50 users. From their documentation it seems to me that they would also start to review my production application once my app actually hits (or maybe nears?) 50 users. So if it actually gets traction and hit 50 users before it gets approved by Dropbox, new users won&amp;rsquo;t be able to auth, I guess (If any Dropbox engineer is reading this, please expedite the review of my app 🙇). &lt;em&gt;UPDATE: It&amp;rsquo;s now approved by Dropbox on 2023-12-28!&lt;/em&gt;&lt;/p&gt;</description><author>La Vita è Bear</author><pubDate>Fri, 22 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://b.yuxuan.org/url2epub-dropbox</guid></item><item><title>How generative AI has fit into my workflows</title><link>https://liza.io/how-generative-ai-has-fit-into-my-workflows/</link><description>&lt;p&gt;I was always excited about the possibilities recent developments in LLM-powered workflows provide. At the same time, I was also wary about the larger implications of these things, from the whole &lt;em&gt;theft&lt;/em&gt; problem to wondering &amp;ldquo;I wonder when this thing is going to become self-aware&amp;rdquo;.&lt;/p&gt;</description><author>Liza Shulyayeva</author><pubDate>Fri, 22 Dec 2023 00:01:33 GMT</pubDate><guid isPermaLink="true">https://liza.io/how-generative-ai-has-fit-into-my-workflows/</guid></item><item><title>Podcast: IaC in depth with Robert Hafner</title><link>https://blog.tedivm.com/appearances/2023/12/podcast-iac-in-depth-with-robert-hafner/</link><description>Robert Hafner on The IaC Podcast.</description><author>tedious ramblings</author><pubDate>Thu, 21 Dec 2023 16:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.tedivm.com/appearances/2023/12/podcast-iac-in-depth-with-robert-hafner/</guid></item><item><title>Embeddings in a nutshell</title><link>https://kaszkowiak.org/en/blog/embeddings/</link><description>What are those embeddings you hear about every day? Find out in this article - many buzzwords will cease to be black magic 🧙</description><author>Blog on Maciej Kaszkowiak</author><pubDate>Thu, 21 Dec 2023 08:00:00 GMT</pubDate><guid isPermaLink="true">https://kaszkowiak.org/en/blog/embeddings/</guid></item><item><title>Media Reflection of 2023</title><link>https://www.spencerharston.com/posts/2023/media-reflection-of-2023/</link><description>&lt;p&gt;I decided this year to look back on the stuff I've read, watched, played, or listened to. While not everything I've consumed is listed here, I did want to point out some items that I enjoyed. No full reviews here, I prefer simple lists and quick thoughts. Gives me an excuse for another blog post at least.&lt;/p&gt;
&lt;h2 id="books" tabindex="-1"&gt;Books&lt;/h2&gt;
&lt;p&gt;See my full list of books in my &lt;a href="https://www.spencerharston.com/posts/books-of-2023" rel="noreferrer" target="_blank"&gt;Books of 2023&lt;/a&gt; post.&lt;/p&gt;
&lt;h3 id="i'm-glad-my-mom-died-by-jennette-mccurdy" tabindex="-1"&gt;&lt;em&gt;I'm Glad My Mom Died&lt;/em&gt; by Jennette McCurdy&lt;/h3&gt;
&lt;p&gt;To be honest, I picked this up due to the immense popularity this book gained on release last year and wanted to see what all the fuss was. I was only on hold for the ebook at the library for five months, but it was worth it. I never watched &lt;em&gt;iCarly&lt;/em&gt; (didn't have cable growing up) so I didn't read this for the nostalgia, but rather the perspective a child actor had in show business. And boy, is it rough. I'm sure some child actors have great experiences but it really takes its toll on those kids. I was also raised Mormon, so I understood some of the experiences McCurdy had, and how it shaped her life and her mother's actions. Fantastic memoir, highly recommended.&lt;/p&gt;
&lt;h3 id="how-to-take-over-the-world-by-ryan-north" tabindex="-1"&gt;&lt;em&gt;How to Take Over the World&lt;/em&gt; by Ryan North&lt;/h3&gt;
&lt;p&gt;I follow Ryan North's &lt;a href="https://www.smbc-comics.com/" rel="noreferrer" target="_blank"&gt;SMBC comic&lt;/a&gt; but I didn't know he wrote books. This book was available at my library and I was on paternity leave so I figured I had some time to read it. I enjoy these types of non-fiction, humorous, science-y type books and North's work fit perfectly in that genre. Chances are I will &lt;strong&gt;not&lt;/strong&gt; be taking over the world, but should the opportunity present itself, I'll be just a bit more prepared.&lt;/p&gt;
&lt;h3 id="all-the-sinners-bleed-by-s.a.-cosby" tabindex="-1"&gt;&lt;em&gt;All the Sinners Bleed&lt;/em&gt; by S.A. Cosby&lt;/h3&gt;
&lt;p&gt;Some years ago, I stumbled across S.A. Cosby's book, &lt;em&gt;Blacktop Wasteland&lt;/em&gt;, at the library (I judged the book by its cover). I read it and thoroughly enjoyed Cosby's writing. I've since read all his books (except &lt;em&gt;My Darkest Prayer&lt;/em&gt;) and his latest, &lt;em&gt;All the Sinners Bleed&lt;/em&gt;, was an enjoyable thriller. I liked the perspective Cosby provided via the characters, and trying to tackle current events. Looking forward to his next book.&lt;/p&gt;
&lt;p&gt;Additionally, I've also started listening to audiobooks, thanks to self-hosting the &lt;a href="https://www.audiobookshelf.org/" rel="noreferrer" target="_blank"&gt;Audiobookshelf server&lt;/a&gt;. The only books I've listened to so far are the &lt;em&gt;Harry Potter&lt;/em&gt; series. It started out as testing the deployment of the server, but I ended up just listening to them all. Ah nostalgia. Next up is the &lt;em&gt;The Lord of the Rings&lt;/em&gt; series.&lt;/p&gt;
&lt;h2 id="music" tabindex="-1"&gt;Music&lt;/h2&gt;
&lt;p&gt;Here are some artists/bands that I enjoyed listening to this year. You can see all my listening habits over on &lt;a href="https://www.last.fm/user/spencer314" rel="noreferrer" target="_blank"&gt;last.fm&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="metric" tabindex="-1"&gt;Metric&lt;/h3&gt;
&lt;p&gt;I've only listened to some of Metric's old stuff before this year, but their album &lt;em&gt;Art of Doubt&lt;/em&gt; popped up in the algorithm and I was hooked. I try to listen to all their albums regularly. Standout track for me is "Risk".&lt;/p&gt;
&lt;h3 id="the-national" tabindex="-1"&gt;The National&lt;/h3&gt;
&lt;p&gt;Don't judge me but the only reason I started listening to The National was due to their song "The Alcott" featuring Taylor Swift. That song has been on repeat a few times for me this year, but I've enjoyed the rest of the album, &lt;em&gt;First Two Pages of Frankenstein&lt;/em&gt; as well. The rest of their discography has started popping up in my rotations lately. Standout track is "The Alcott (feat. Taylor Swift)".&lt;/p&gt;
&lt;h3 id="cage-the-elephant" tabindex="-1"&gt;Cage the Elephant&lt;/h3&gt;
&lt;p&gt;Similar to Metric, I've only heard the early songs of Cage the Elephant, but some of their newer albums popped up in my recommendations so I gave them a go. Their albums &lt;em&gt;Tell Me I'm Pretty&lt;/em&gt; and &lt;em&gt;Social Cues&lt;/em&gt; were a common occurrence in my music habits this year. Standout tracks are "Ready to Let Go" and "Social Cues".&lt;/p&gt;
&lt;h3 id="future-islands" tabindex="-1"&gt;Future Islands&lt;/h3&gt;
&lt;p&gt;This was a new band I had never heard before. Something about the voice of the lead singer was rather unique and I enjoyed it. Played a few tracks here and there on repeat. Standout track is "A Dream of You and Me".&lt;/p&gt;
&lt;h3 id="yiruma" tabindex="-1"&gt;Yiruma&lt;/h3&gt;
&lt;p&gt;At some point in passing, I heard the song "River Flows in You". I didn't know what it was, but thanks to my Pixel's Now Playing feature, I found this amazing piano player. I play the album &lt;em&gt;The Best - Reminiscent 10th Anniversary&lt;/em&gt;, of which contains the above song, when I need some relaxing, modern classical music. Standout tracks are "River Flows in You" and "May Be".&lt;/p&gt;
&lt;h3 id="chvrches" tabindex="-1"&gt;CHVRCHES&lt;/h3&gt;
&lt;p&gt;I've been on a bit of a kick trying to listen to more bands featuring female lead singers (thanks to listening to a lot of Metric). CHVRCHES was the one band that kept coming up and I've listened to many of their songs. I'm sold. Standout track is "How Not To Drown (feat. Robert Smith)".&lt;/p&gt;
&lt;h3 id="broken-bells" tabindex="-1"&gt;Broken Bells&lt;/h3&gt;
&lt;p&gt;And yet one more instance of re-picking up a band that I hadn't listened to in years. The albums &lt;em&gt;After the Disco&lt;/em&gt; and &lt;em&gt;INTO THE BLUE&lt;/em&gt; are great albums. Standout track is "Good Luck".&lt;/p&gt;
&lt;h3 id="honorable-mention" tabindex="-1"&gt;Honorable Mention&lt;/h3&gt;
&lt;p&gt;I just wanted to share a song that I stumbled across on reddit of all places. It's "Can't Get You Out Of My Head (ft. Parcels)" by AnnenMayKantereit. It's a cover of the immortal song by Kylie Minogue, and dangit, &lt;em&gt;I can't get it out of my head&lt;/em&gt;. My tracking isn't accurate because it's only available on YouTube, but it's probably in my top five played songs this year.&lt;/p&gt;
&lt;details&gt;
Click to open a YouTube embed of "Can't Get You Out of My Head (Cover)"

&lt;/details&gt;
&lt;h2 id="podcasts" tabindex="-1"&gt;Podcasts&lt;/h2&gt;
&lt;p&gt;I need to find some new podcasts, but here's a couple I've listened to this year.&lt;/p&gt;
&lt;h3 id="darknet-diaries" tabindex="-1"&gt;&lt;em&gt;Darknet Diaries&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;Currently, &lt;em&gt;Darknet Diaries&lt;/em&gt; is the only podcast that I have notifications set up to alert me about new episodes. There's some really fantastic stories being told, and has definitely made me more aware of my own cybersecurity and general Internet habits. I especially enjoyed the episodes about game console hacking and the people who created modchips, game hackers, and the one about the money counterfeiter (episode 102).&lt;/p&gt;
&lt;h3 id="the-nateland-podcast" tabindex="-1"&gt;&lt;em&gt;The Nateland Podcast&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;A coworker mentioned that the comedian Nate Bargatze was one of the few "clean" comedians. I started listening to his podcast when I needed some light-hearted background chatter. I'd totally go to one of his shows, should he come back to Utah.&lt;/p&gt;
&lt;h3 id="search-engine" tabindex="-1"&gt;&lt;em&gt;Search Engine&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;One of my favorite podcasts of all time is &lt;em&gt;Reply All&lt;/em&gt;, but due to some controversies, the podcast lost one of its hosts, PJ Vogt. Once that died down, he started &lt;em&gt;Search Engine&lt;/em&gt;. While I don't know the full story of what happened, I missed &lt;em&gt;Reply All&lt;/em&gt;. I'd say &lt;em&gt;Search Engine&lt;/em&gt; is adjacent to &lt;em&gt;Reply All&lt;/em&gt;, covering similar stories. So for now it fills some of that void. I'll pick up new episodes when they sound interesting to me. I think I'll also go back to some of the &lt;em&gt;Reply All&lt;/em&gt; archives in the meantime...&lt;/p&gt;
&lt;h2 id="movies" tabindex="-1"&gt;Movies&lt;/h2&gt;
&lt;p&gt;I need to track this section better. last.fm has been great for tracking music, but I think I want to check out one of those movie/TV tracking services. The following is from memory, and there's definitely more than what I've listed here.&lt;/p&gt;
&lt;h3 id="spider-man%3A-across-the-spider-verse" tabindex="-1"&gt;&lt;em&gt;Spider-Man: Across the Spider-Verse&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;If you're like me and have Superhero Fatigue™, I get it. I don't watch much anything anymore in this genre, post-&lt;em&gt;Endgame&lt;/em&gt;. But if there's one series of superhero movies to watch, it's the Spider-Man Spider-Verse movies. The first one was amazing, and this sequel is no different. &lt;em&gt;Across the Spider-Verse&lt;/em&gt; continues the story of Miles and totally delivers. If nothing else, watch it for the animation quality alone. In this time of AI-generated nonsense, it's refreshing to see some true homegrown art.&lt;/p&gt;
&lt;h3 id="oppenheimer" tabindex="-1"&gt;&lt;em&gt;Oppenheimer&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;In college, I read the book this movie is based on, &lt;em&gt;American Prometheus&lt;/em&gt;. I was very excited to see this adaptation come to the big screen, and it did not disappoint. The performance of Oppenheimer by Cillian Murphy was on point. A very emotional and thought provoking movie. I'm glad I saw this one in theaters too.&lt;/p&gt;
&lt;h3 id="barbie" tabindex="-1"&gt;&lt;em&gt;Barbie&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Barbie&lt;/em&gt; was one of my favorite movies this year. I did laugh many times, and the satire was perfect. Margot Robbie and Ryan Gosling (and everyone else) did a fantastic job. Remember, you are Kenough.&lt;/p&gt;
&lt;h3 id="the-gray-man" tabindex="-1"&gt;&lt;em&gt;The Gray Man&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;I have soft spot for spy-thriller movies, and apparently I enjoy a lot of movies starring Ryan Gosling. &lt;em&gt;The Gray Man&lt;/em&gt; was a solid entry in the thriller genre. The scenes with both Gosling and Chris Evans were fantastic.&lt;/p&gt;
&lt;h3 id="bullet-train" tabindex="-1"&gt;&lt;em&gt;Bullet Train&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;The spy-thriller genre strikes again. The cast was amazing in this movie. I'm not usually a Brad Pitt fan (the &lt;em&gt;Ocean Eleven&lt;/em&gt; series notwithstanding) but he was the star of the show. Well, him and the duo of Aaron Taylor-Johnson and Brian Tyree Henry as Tangerine and Lemon. Will need to watch again.&lt;/p&gt;
&lt;h2 id="tv-shows" tabindex="-1"&gt;TV Shows&lt;/h2&gt;
&lt;p&gt;Next year I'm also going to try and track these better. I swear there was a show I really enjoyed this year, but I can't remember what it was...&lt;/p&gt;
&lt;h3 id="better-call-saul%2C-full-series" tabindex="-1"&gt;&lt;em&gt;Better Call Saul&lt;/em&gt;, full series&lt;/h3&gt;
&lt;p&gt;I started watching &lt;em&gt;Better Call Saul&lt;/em&gt; a couple years ago after finishing &lt;em&gt;Breaking Bad&lt;/em&gt;. It was only this year that I finished latter half of the show. An absolute hit, one of my favorite shows (along with &lt;em&gt;Breaking Bad&lt;/em&gt;). I daresay I kinda liked &lt;em&gt;Better Call Saul&lt;/em&gt; over &lt;em&gt;Breaking Bad&lt;/em&gt;, mostly due the amazing performances of Bob Odenkirk and Rhea Seehorn. 10/10, would watch again.&lt;/p&gt;
&lt;h3 id="blue-eye-samurai%2C-season-1" tabindex="-1"&gt;&lt;em&gt;BLUE EYE SAMURAI&lt;/em&gt;, season 1&lt;/h3&gt;
&lt;p&gt;This show was an unexpected favorite for me. I'm usually not into animated shows, but the first episode was posted to YouTube by Netflix, and it hooked me. The story was great, characters well-written and developed, and the animation was gorgeous (I think this was the clincher) and soundtrack perfect. You never know what shows Netflix will cancel after the first season, but it has been confirmed a second season is coming.&lt;/p&gt;
&lt;h3 id="the-great-british-baking-show%2C-collection-11" tabindex="-1"&gt;&lt;em&gt;The Great British Baking Show&lt;/em&gt;, collection 11&lt;/h3&gt;
&lt;p&gt;I look forward to the new season of &lt;em&gt;The Great British Baking Show&lt;/em&gt; every year, and this season was excellent as usual. I miss the duo of Noel and Matt, but the new host, Alison has kept up with the shenanigans pretty well.&lt;/p&gt;
&lt;h3 id="ahsoka%2C-season-1" tabindex="-1"&gt;&lt;em&gt;Ahsoka&lt;/em&gt;, season 1&lt;/h3&gt;
&lt;p&gt;I went into this show fairly blind. I've never watched the animated Star Wars shows (&lt;em&gt;Clone Wars&lt;/em&gt;, &lt;em&gt;Rebels&lt;/em&gt;), but I was faintly aware of who the character Ahsoka and her role in the greater Star Wars stories. &lt;em&gt;Ahsoka&lt;/em&gt; turned out pretty well (much better than &lt;em&gt;The Book of Boba Fett&lt;/em&gt;), and I enjoyed it. Looking forward to season 2, need to find out what happens to Baylan Skoll (RIP Ray Stevenson) and Shin Hati...&lt;/p&gt;
&lt;h3 id="barbecue-showdown%2C-seasons-1-%26-2" tabindex="-1"&gt;&lt;em&gt;Barbecue Showdown&lt;/em&gt;, seasons 1 &amp;amp; 2&lt;/h3&gt;
&lt;p&gt;I'm pretty sure this show was created by BIG BARBECUE to try and convince me to buy a smoker. And it's working.&lt;/p&gt;
&lt;h2 id="games" tabindex="-1"&gt;Games&lt;/h2&gt;
&lt;p&gt;Haven't dedicated much time to gaming this year, with having a new kid and all, but here are some of the games I enjoyed playing. This list would probably be half this if it wasn't for getting a Steam Deck. Totally worth it.&lt;/p&gt;
&lt;h3 id="tony-hawk's-pro-skater-1%2B2" tabindex="-1"&gt;Tony Hawk's Pro Skater 1+2&lt;/h3&gt;
&lt;p&gt;I've been waiting for the day this was no longer an Epic exclusive (bleh) and it finally made its way to Steam this year. Fantastic remaster of the classic games, killer soundtrack, and runs fantastic on the Deck.&lt;/p&gt;
&lt;h3 id="brotato" tabindex="-1"&gt;Brotato&lt;/h3&gt;
&lt;p&gt;Out of the &lt;em&gt;Vampire Survivors&lt;/em&gt;-inspired clones, this one is pretty simple and straightforward. Lots of fun! Though out of my six-ish hours of playing, I've only managed to get to get to level 20 once...&lt;/p&gt;
&lt;h3 id="fallout-4" tabindex="-1"&gt;Fallout 4&lt;/h3&gt;
&lt;p&gt;One of my favorite games of all time is &lt;em&gt;Fallout 3&lt;/em&gt;, so decided to try and make progress in &lt;em&gt;Fallout 4&lt;/em&gt;, especially now with a Deck (works great by the way). Certainly got further in the game, but so much to do, and I keep finding all the side quests. Despite that, I like &lt;em&gt;Fallout 4&lt;/em&gt;, and one day I'll complete it.&lt;/p&gt;
&lt;h3 id="slay-the-spire" tabindex="-1"&gt;Slay the Spire&lt;/h3&gt;
&lt;p&gt;I've had this one for a couple years, but I picked it back up once I got my Deck. And I finally managed to beat the final boss, the Corrupted Heart. It was one of my most satisfying gaming moments. Been plugging away at the Ascensions (currently at Ascension 12) lately. Slowly, but surely. One of the best games I've played. Ironclad for life.&lt;/p&gt;
&lt;h3 id="rocket-league" tabindex="-1"&gt;Rocket League&lt;/h3&gt;
&lt;p&gt;I'm only including this because I love &lt;em&gt;Rocket League&lt;/em&gt; (1300+ hours), or at least I loved the game it once was. While the core gameplay is the same, the post-Epic acquisition has left it a shell of its former indie self. I used to buy keys and the Rocket Pass, but I haven't spent a dime on it since Psyonix was bought out. And ever since Psyonix/Epic announced they were removing player-to-player trading, I simply stopped playing. I may pick it back up one day, though I feel its days are numbered, either being absorbed into &lt;em&gt;Fortnite&lt;/em&gt; (bleh) or &lt;em&gt;Rocket League 2&lt;/em&gt;. Well played! gg&lt;/p&gt;
&lt;p&gt;For posterity, here's my Steam 2023 Year in Review&lt;/p&gt;
&lt;figure&gt;&lt;source type="image/webp" /&gt;&lt;img alt="2023 steam year in review" height="523" src="https://www.spencerharston.com/assets/img/content/games-of-2023-480w.jpeg" width="1000" /&gt;&lt;figcaption&gt;Steam 2023 Year in Review&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id="next-year" tabindex="-1"&gt;Next Year&lt;/h2&gt;
&lt;p&gt;I don't really have anything I'm looking forward to. The only show I was waiting for was &lt;em&gt;Andor&lt;/em&gt;, season 2, which was set to be released in August 2024, but as of today, it's been delayed. Probably to sometime in 2025. Maybe if &lt;em&gt;The Umbrella Academy&lt;/em&gt; releases its final season...&lt;/p&gt;</description><author>Spencer Harston</author><pubDate>Thu, 21 Dec 2023 07:45:00 GMT</pubDate><guid isPermaLink="true">https://www.spencerharston.com/posts/2023/media-reflection-of-2023/</guid></item><item><title>HTML inert property and React fallback</title><link>https://muffinman.io/blog/html-inert-property/</link><description>&lt;article class="article"&gt;&lt;p&gt;HTML &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/inert"&gt;inert&lt;/a&gt; is a relatively new property, but it is supported in &lt;a href="https://caniuse.com/mdn-api_htmlelement_inert"&gt;all major browsers&lt;/a&gt; since April this year.&lt;/p&gt;
&lt;p&gt;When you set &lt;code&gt;inert&lt;/code&gt; on an element, the browser will ignore all user events on it, including tabbing into elements. It will also hide it from screen readers. I like to think of it as a &amp;quot;reversed focus trap&amp;quot;. But we can use it to create focus traps for modals by setting &lt;code&gt;inert&lt;/code&gt; on the main content.&lt;/p&gt;
&lt;div&gt;&lt;p&gt;This div has &lt;code&gt;inert&lt;/code&gt; property set and if your browser supports it, you won&amp;#x27;t be able to interact with the button and link below.&lt;/p&gt;&lt;div style="margin-top: 10px;"&gt;&lt;button class="btn btn--sm" style="display: inline-block;"&gt;You can&amp;#x27;t click me&lt;/button&gt;&lt;a href="/" style="margin-left: 20px;"&gt;Nor me!&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id="react-fallback-component"&gt;React fallback component &lt;a class="anchor-link" href="#react-fallback-component"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before I learned about &lt;code&gt;inert&lt;/code&gt;, I built a relatively simple component to achieve the same result.&lt;/p&gt;
&lt;p&gt;Now I prefer to use the native solution, but on most projects, we still have to support older browsers. My approach is to handle it in the wrapper component, which finds all focusable elements and sets &lt;code&gt;tabindex=&amp;quot;-1&amp;quot;&lt;/code&gt;. After mounting, the mutation observer starts listening and re-sets the tabindex when content is changed.&lt;/p&gt;
&lt;p&gt;The wrapper itself has &lt;code&gt;aria-hidden=&amp;quot;true&amp;quot;&lt;/code&gt;, which hides it from screen readers.&lt;/p&gt;
&lt;p&gt;You can try it out on &lt;a href="https://codepen.io/stanko/pen/VwRZeVN"&gt;CodePen&lt;/a&gt; and see the source code below:&lt;/p&gt;
&lt;div class="spoiler"&gt;&lt;div class="spoiler__content-wrapper" id="mpf7r89m-o4pmsi"&gt;&lt;div class="spoiler__content"&gt;&lt;pre class="language-tsx"&gt;&lt;code class="language-tsx code-highlight"&gt;&lt;span class="code-line"&gt;&lt;span class="token keyword"&gt;const&lt;/span&gt; focusableElementsSelector &lt;span class="token operator"&gt;=&lt;/span&gt; &lt;span class="token punctuation"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token string"&gt;&amp;quot;a[href]&amp;quot;&lt;/span&gt;&lt;span class="token punctuation"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token string"&gt;&amp;quot;input&amp;quot;&lt;/span&gt;&lt;span class="token punctuation"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token string"&gt;&amp;quot;select&amp;quot;&lt;/span&gt;&lt;span class="token punctuation"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token string"&gt;&amp;quot;textarea&amp;quot;&lt;/span&gt;&lt;span class="token punctuation"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token string"&gt;&amp;quot;button&amp;quot;&lt;/span&gt;&lt;span class="token punctuation"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token string"&gt;&amp;quot;audio[controls]&amp;quot;&lt;/span&gt;&lt;span class="token punctuation"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token string"&gt;&amp;quot;video[controls]&amp;quot;&lt;/span&gt;&lt;span class="token punctuation"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token string"&gt;&amp;quot;details &amp;gt; summary:first-of-type&amp;quot;&lt;/span&gt;&lt;span class="token punctuation"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token string"&gt;&amp;quot;details&amp;quot;&lt;/span&gt;&lt;span class="token punctuation"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token string"&gt;&amp;quot;[contenteditable]:not([contenteditable=\&amp;quot;false\&amp;quot;])&amp;quot;&lt;/span&gt;&lt;span class="token punctuation"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token string"&gt;&amp;quot;[tabindex]:not([tabindex=\&amp;quot;-1\&amp;quot;])&amp;quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;&lt;span class="token punctuation"&gt;]&lt;/span&gt;&lt;span class="token punctuation"&gt;.&lt;/span&gt;&lt;span class="token method function property-access"&gt;join&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;&lt;span class="token string"&gt;&amp;quot;, &amp;quot;&lt;/span&gt;&lt;span class="token punctuation"&gt;)&lt;/span&gt;&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;&lt;span class="token keyword"&gt;const&lt;/span&gt; &lt;span class="token function-variable function"&gt;addTabIndex&lt;/span&gt; &lt;span class="token operator"&gt;=&lt;/span&gt; &lt;span class="token punctuation"&gt;(&lt;/span&gt;$wrapper&lt;span class="token punctuation"&gt;)&lt;/span&gt; &lt;span class="token arrow operator"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="token punctuation"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  $wrapper&lt;span class="token punctuation"&gt;.&lt;/span&gt;&lt;span class="token method function property-access"&gt;querySelectorAll&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;focusableElementsSelector&lt;span class="token punctuation"&gt;)&lt;/span&gt;&lt;span class="token punctuation"&gt;.&lt;/span&gt;&lt;span class="token method function property-access"&gt;forEach&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;$element&lt;span class="token punctuation"&gt;)&lt;/span&gt; &lt;span class="token arrow operator"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="token punctuation"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;    $element&lt;span class="token punctuation"&gt;.&lt;/span&gt;&lt;span class="token method function property-access"&gt;setAttribute&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;&lt;span class="token string"&gt;&amp;quot;tabindex&amp;quot;&lt;/span&gt;&lt;span class="token punctuation"&gt;,&lt;/span&gt; &lt;span class="token operator"&gt;-&lt;/span&gt;&lt;span class="token number"&gt;1&lt;/span&gt;&lt;span class="token punctuation"&gt;)&lt;/span&gt;&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token punctuation"&gt;}&lt;/span&gt;&lt;span class="token punctuation"&gt;)&lt;/span&gt;&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;&lt;span class="token punctuation"&gt;}&lt;/span&gt;&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;&lt;span class="token keyword"&gt;const&lt;/span&gt; &lt;span class="token function-variable function"&gt;removeTabIndex&lt;/span&gt; &lt;span class="token operator"&gt;=&lt;/span&gt; &lt;span class="token punctuation"&gt;(&lt;/span&gt;$wrapper&lt;span class="token punctuation"&gt;)&lt;/span&gt; &lt;span class="token arrow operator"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="token punctuation"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  $wrapper&lt;span class="token punctuation"&gt;.&lt;/span&gt;&lt;span class="token method function property-access"&gt;querySelectorAll&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;focusableElementsSelector&lt;span class="token punctuation"&gt;)&lt;/span&gt;&lt;span class="token punctuation"&gt;.&lt;/span&gt;&lt;span class="token method function property-access"&gt;forEach&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;$element&lt;span class="token punctuation"&gt;)&lt;/span&gt; &lt;span class="token arrow operator"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="token punctuation"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;    $element&lt;span class="token punctuation"&gt;.&lt;/span&gt;&lt;span class="token method function property-access"&gt;removeAttribute&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;&lt;span class="token string"&gt;&amp;quot;tabindex&amp;quot;&lt;/span&gt;&lt;span class="token punctuation"&gt;)&lt;/span&gt;&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token punctuation"&gt;}&lt;/span&gt;&lt;span class="token punctuation"&gt;)&lt;/span&gt;&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;&lt;span class="token punctuation"&gt;}&lt;/span&gt;&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;&lt;span class="token keyword"&gt;const&lt;/span&gt; &lt;span class="token function-variable function"&gt;Inert&lt;/span&gt; &lt;span class="token operator"&gt;=&lt;/span&gt; &lt;span class="token punctuation"&gt;(&lt;/span&gt;&lt;span class="token punctuation"&gt;{&lt;/span&gt; enabled&lt;span class="token punctuation"&gt;,&lt;/span&gt; children&lt;span class="token punctuation"&gt;,&lt;/span&gt; &lt;span class="token spread operator"&gt;...&lt;/span&gt;props &lt;span class="token punctuation"&gt;}&lt;/span&gt;&lt;span class="token punctuation"&gt;)&lt;/span&gt; &lt;span class="token arrow operator"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="token punctuation"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token keyword"&gt;const&lt;/span&gt; wrapperRef &lt;span class="token operator"&gt;=&lt;/span&gt; &lt;span class="token function"&gt;useRef&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;&lt;span class="token keyword"&gt;null&lt;/span&gt;&lt;span class="token punctuation"&gt;)&lt;/span&gt;&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token keyword"&gt;const&lt;/span&gt; observerRef &lt;span class="token operator"&gt;=&lt;/span&gt; &lt;span class="token function"&gt;useRef&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;    &lt;span class="token keyword"&gt;new&lt;/span&gt; &lt;span class="token class-name"&gt;MutationObserver&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;&lt;span class="token punctuation"&gt;)&lt;/span&gt; &lt;span class="token arrow operator"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="token function"&gt;addTabIndex&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;wrapper&lt;span class="token punctuation"&gt;.&lt;/span&gt;&lt;span class="token property-access"&gt;current&lt;/span&gt;&lt;span class="token punctuation"&gt;)&lt;/span&gt;&lt;span class="token punctuation"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token punctuation"&gt;)&lt;/span&gt;&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token function"&gt;useEffect&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;&lt;span class="token punctuation"&gt;)&lt;/span&gt; &lt;span class="token arrow operator"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="token punctuation"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;    &lt;span class="token keyword"&gt;if&lt;/span&gt; &lt;span class="token punctuation"&gt;(&lt;/span&gt;enabled&lt;span class="token punctuation"&gt;)&lt;/span&gt; &lt;span class="token punctuation"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;      &lt;span class="token keyword"&gt;if&lt;/span&gt; &lt;span class="token punctuation"&gt;(&lt;/span&gt;wrapperRef&lt;span class="token punctuation"&gt;.&lt;/span&gt;&lt;span class="token property-access"&gt;current&lt;/span&gt;&lt;span class="token punctuation"&gt;)&lt;/span&gt; &lt;span class="token punctuation"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;        &lt;span class="token comment"&gt;// Add tabindex&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;        &lt;span class="token function"&gt;addTabIndex&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;wrapperRef&lt;span class="token punctuation"&gt;.&lt;/span&gt;&lt;span class="token property-access"&gt;current&lt;/span&gt;&lt;span class="token punctuation"&gt;)&lt;/span&gt;&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;        &lt;span class="token comment"&gt;// Start observing&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;        observerRef&lt;span class="token punctuation"&gt;.&lt;/span&gt;&lt;span class="token property-access"&gt;current&lt;/span&gt;&lt;span class="token punctuation"&gt;.&lt;/span&gt;&lt;span class="token method function property-access"&gt;observe&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;wrapperRef&lt;span class="token punctuation"&gt;.&lt;/span&gt;&lt;span class="token property-access"&gt;current&lt;/span&gt;&lt;span class="token punctuation"&gt;,&lt;/span&gt; &lt;span class="token punctuation"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;          childList&lt;span class="token operator"&gt;:&lt;/span&gt; &lt;span class="token boolean"&gt;true&lt;/span&gt;&lt;span class="token punctuation"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;          subtree&lt;span class="token operator"&gt;:&lt;/span&gt; &lt;span class="token boolean"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;        &lt;span class="token punctuation"&gt;}&lt;/span&gt;&lt;span class="token punctuation"&gt;)&lt;/span&gt;&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;      &lt;span class="token punctuation"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;    &lt;span class="token punctuation"&gt;}&lt;/span&gt; &lt;span class="token keyword"&gt;else&lt;/span&gt; &lt;span class="token punctuation"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;      &lt;span class="token comment"&gt;// Disconnect the mutation observer&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;      observerRef&lt;span class="token punctuation"&gt;.&lt;/span&gt;&lt;span class="token property-access"&gt;current&lt;/span&gt;&lt;span class="token punctuation"&gt;.&lt;/span&gt;&lt;span class="token method function property-access"&gt;disconnect&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;&lt;span class="token punctuation"&gt;)&lt;/span&gt;&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;      &lt;span class="token comment"&gt;// And remove the attributes which we added&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;      &lt;span class="token function"&gt;removeTabIndex&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;wrapperRef&lt;span class="token punctuation"&gt;.&lt;/span&gt;&lt;span class="token property-access"&gt;current&lt;/span&gt;&lt;span class="token punctuation"&gt;)&lt;/span&gt;&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;    &lt;span class="token punctuation"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token punctuation"&gt;}&lt;/span&gt;&lt;span class="token punctuation"&gt;,&lt;/span&gt; &lt;span class="token punctuation"&gt;[&lt;/span&gt;enabled&lt;span class="token punctuation"&gt;,&lt;/span&gt; wrapperRef&lt;span class="token punctuation"&gt;.&lt;/span&gt;&lt;span class="token property-access"&gt;current&lt;/span&gt;&lt;span class="token punctuation"&gt;]&lt;/span&gt;&lt;span class="token punctuation"&gt;)&lt;/span&gt;&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token keyword"&gt;return&lt;/span&gt; &lt;span class="token punctuation"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;    &lt;span class="token tag"&gt;&lt;span class="token tag"&gt;&lt;span class="token punctuation"&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="code-line"&gt;&lt;span class="token tag"&gt;      &lt;span class="token spread"&gt;&lt;span class="token punctuation"&gt;{&lt;/span&gt;&lt;span class="token spread operator"&gt;...&lt;/span&gt;props&lt;span class="token punctuation"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="code-line"&gt;&lt;span class="token tag"&gt;      &lt;span class="token attr-name"&gt;ref&lt;/span&gt;&lt;span class="token script language-javascript"&gt;&lt;span class="token script-punctuation punctuation"&gt;=&lt;/span&gt;&lt;span class="token punctuation"&gt;{&lt;/span&gt;wrapperRef&lt;span class="token punctuation"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="code-line"&gt;&lt;span class="token tag"&gt;      &lt;span class="token attr-name"&gt;inert&lt;/span&gt;&lt;span class="token script language-javascript"&gt;&lt;span class="token script-punctuation punctuation"&gt;=&lt;/span&gt;&lt;span class="token punctuation"&gt;{&lt;/span&gt;enabled &lt;span class="token operator"&gt;?&lt;/span&gt; &lt;span class="token string"&gt;&amp;quot;&amp;quot;&lt;/span&gt; &lt;span class="token operator"&gt;:&lt;/span&gt; &lt;span class="token keyword"&gt;null&lt;/span&gt;&lt;span class="token punctuation"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="code-line"&gt;&lt;span class="token tag"&gt;      &lt;span class="token attr-name"&gt;tabindex&lt;/span&gt;&lt;span class="token script language-javascript"&gt;&lt;span class="token script-punctuation punctuation"&gt;=&lt;/span&gt;&lt;span class="token punctuation"&gt;{&lt;/span&gt;enabled &lt;span class="token operator"&gt;?&lt;/span&gt; &lt;span class="token operator"&gt;-&lt;/span&gt;&lt;span class="token number"&gt;1&lt;/span&gt; &lt;span class="token operator"&gt;:&lt;/span&gt; &lt;span class="token keyword"&gt;null&lt;/span&gt;&lt;span class="token punctuation"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="code-line"&gt;&lt;span class="token tag"&gt;      &lt;span class="token attr-name"&gt;aria-hidden&lt;/span&gt;&lt;span class="token script language-javascript"&gt;&lt;span class="token script-punctuation punctuation"&gt;=&lt;/span&gt;&lt;span class="token punctuation"&gt;{&lt;/span&gt;enabled&lt;span class="token punctuation"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="code-line"&gt;&lt;span class="token tag"&gt;      &lt;span class="token attr-name"&gt;style&lt;/span&gt;&lt;span class="token script language-javascript"&gt;&lt;span class="token script-punctuation punctuation"&gt;=&lt;/span&gt;&lt;span class="token punctuation"&gt;{&lt;/span&gt;&lt;span class="token punctuation"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="code-line"&gt;&lt;span class="token tag"&gt;&lt;span class="token script language-javascript"&gt;        pointerEvents&lt;span class="token operator"&gt;:&lt;/span&gt; enabled &lt;span class="token operator"&gt;?&lt;/span&gt; &lt;span class="token string"&gt;&amp;quot;none&amp;quot;&lt;/span&gt; &lt;span class="token operator"&gt;:&lt;/span&gt; &lt;span class="token keyword"&gt;null&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="code-line"&gt;&lt;span class="token tag"&gt;&lt;span class="token script language-javascript"&gt;      &lt;span class="token punctuation"&gt;}&lt;/span&gt;&lt;span class="token punctuation"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="code-line"&gt;&lt;span class="token tag"&gt;    &lt;span class="token punctuation"&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="token plain-text"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="code-line"&gt;&lt;span class="token plain-text"&gt;      &lt;/span&gt;&lt;span class="token punctuation"&gt;{&lt;/span&gt;children&lt;span class="token punctuation"&gt;}&lt;/span&gt;&lt;span class="token plain-text"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="code-line"&gt;&lt;span class="token plain-text"&gt;    &lt;/span&gt;&lt;span class="token tag"&gt;&lt;span class="token tag"&gt;&lt;span class="token punctuation"&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class="token punctuation"&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token punctuation"&gt;)&lt;/span&gt;&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;&lt;span class="token punctuation"&gt;}&lt;/span&gt;&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;button class="spoiler__toggle btn btn--sm"&gt;&lt;span class="spoiler__toggle-show"&gt;Show source code&lt;/span&gt;&lt;span class="spoiler__toggle-hide"&gt;Hide source code&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;
&lt;h3 id="drawbacks--edge-cases"&gt;Drawbacks / edge cases &lt;a class="anchor-link" href="#drawbacks--edge-cases"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The component uses a pretty simple approach, and it doesn&amp;#x27;t handle some edge cases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The component doesn&amp;#x27;t check if elements already have the &lt;code&gt;tabindex&lt;/code&gt; explicitly set.&lt;/li&gt;
&lt;li&gt;When the mutation observer triggers, it goes through all of the elements again.&lt;/li&gt;
&lt;li&gt;Depending on your needs, you might need to update &lt;code&gt;focusableElementsSelector&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of that is by design. I wanted to keep it simple to make maintenance easier. I think it is easier to alter specific details on a per-project basis.&lt;/p&gt;
&lt;p&gt;For the same reasons, I won&amp;#x27;t publish it as a npm package. Releasing it as a package would require covering all of these cases and figuring out a flexible public API. I just don&amp;#x27;t think it is worth the effort. But I do hope you will it useful!&lt;/p&gt;&lt;/article&gt;</description><author>Muffin Man</author><pubDate>Thu, 21 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://muffinman.io/blog/html-inert-property/</guid></item><item><title>Upgrading the star and planet generators on Iron Arachne</title><link>https://benovermyer.com/blog/2023/12/upgrading-the-star-and-planet-generators-on-iron-arachne/</link><description>&lt;p&gt;For over a month I've been working on upgrading the graphics that I generate for stars and planets
in the planet, star nation, and star system generators on &lt;a href="https://ironarachne.com" rel="external"&gt;Iron Arachne&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That work is now done and live.&lt;/p&gt;
&lt;p&gt;Instead of setting up a 3D scene, texturing a sphere, and adding cloud layers if appropriate, now
I'm doing everything in a single 2D shader. This was a &lt;em&gt;lot&lt;/em&gt; of math to learn and apply.&lt;/p&gt;
&lt;p&gt;The new shaders have cool additions like atmospheric scattering and much better planetary geographics.&lt;/p&gt;
&lt;p&gt;Here's a comparison of the old version and new version for each of the planet types.&lt;/p&gt;
&lt;img alt="Image of a comparison between the old and new arid planets" class="photo" src="https://benovermyer.com/blog/2023/12/upgrading-the-star-and-planet-generators-on-iron-arachne/arid-planets.jpg" /&gt;
&lt;p&gt;The "arid" planet. Meant to be like a Mars or Arrakis world. The new version has a very thin atmosphere
that you can just barely see. Clouds are almost nonexistent most of the time.&lt;/p&gt;
&lt;img alt="Image of a comparison between the old and new barren planets" class="photo" src="https://benovermyer.com/blog/2023/12/upgrading-the-star-and-planet-generators-on-iron-arachne/barren-planets.jpg" /&gt;
&lt;p&gt;The "barren" planet. This is basically just a rock spinning in space. The old version
had a deformation of the sphere. The new version doesn't. This is the only one that has no atmosphere
in the new version.&lt;/p&gt;
&lt;img alt="Image of a comparison between the old and new garden planets" class="photo" src="https://benovermyer.com/blog/2023/12/upgrading-the-star-and-planet-generators-on-iron-arachne/garden-planets.jpg" /&gt;
&lt;p&gt;The "garden" planet. This one is meant to be Earth-like. The old version was honestly really
ugly.&lt;/p&gt;
&lt;img alt="Image of a comparison between the old and new gas giant planets" class="photo" src="https://benovermyer.com/blog/2023/12/upgrading-the-star-and-planet-generators-on-iron-arachne/gas-giant-planets.jpg" /&gt;
&lt;p&gt;The "gas giant" planet. The new version has very slight atmospheric scattering, a much more vibrant
surface, and variable rotation.&lt;/p&gt;
&lt;img alt="Image of a comparison between the old and new ice planets" class="photo" src="https://benovermyer.com/blog/2023/12/upgrading-the-star-and-planet-generators-on-iron-arachne/ice-planets.jpg" /&gt;
&lt;p&gt;The "ice" planet. The old one was pretty ugly, though I think the new one could stand to have a bit more "blue"
in it. We'll see if I change that in a future pass.&lt;/p&gt;
&lt;img alt="Image of a comparison between the old and new jungle planets" class="photo" src="https://benovermyer.com/blog/2023/12/upgrading-the-star-and-planet-generators-on-iron-arachne/jungle-planets.jpg" /&gt;
&lt;p&gt;The "jungle" planet. This is something the Predator aliens would love. The new version has a better surface and
looks more like a planet.&lt;/p&gt;
&lt;img alt="Image of a comparison between the old and new ocean planets" class="photo" src="https://benovermyer.com/blog/2023/12/upgrading-the-star-and-planet-generators-on-iron-arachne/ocean-planets.jpg" /&gt;
&lt;p&gt;The "ocean" planet. Mon Calamari is a good example of one. The new version just looks better on all fronts. I
gave it some small amount of dry land, too.&lt;/p&gt;
&lt;img alt="Image of a comparison between the old and new swamp planets" class="photo" src="https://benovermyer.com/blog/2023/12/upgrading-the-star-and-planet-generators-on-iron-arachne/swamp-planets.jpg" /&gt;
&lt;p&gt;The "swamp" planet. You're probably familiar with Dagobah at least. The "oceans" now look shallower, among other
things.&lt;/p&gt;
&lt;img alt="Image of a comparison between the old and new toxic planets" class="photo" src="https://benovermyer.com/blog/2023/12/upgrading-the-star-and-planet-generators-on-iron-arachne/toxic-planets.jpg" /&gt;
&lt;p&gt;The "toxic" planet. This time around, I made it look truly icky, with a sickly green atmosphere.&lt;/p&gt;
&lt;img alt="Image of a comparison between the old and new volcanic planets" class="photo" src="https://benovermyer.com/blog/2023/12/upgrading-the-star-and-planet-generators-on-iron-arachne/volcanic-planets.jpg" /&gt;
&lt;p&gt;The "volcanic" planet. Mustafar is an example of one such. I am &lt;em&gt;really&lt;/em&gt; happy with how the new version turned
out. It looks much more visceral and lava-y.&lt;/p&gt;
&lt;img alt="Image of a comparison between the old and new star nation output" class="photo" src="https://benovermyer.com/blog/2023/12/upgrading-the-star-and-planet-generators-on-iron-arachne/star-nation.jpg" /&gt;
&lt;p&gt;The above image is a comparison of the star nation system graphic done the old way (above) and the new way (below).
This really shows some of the other tweaks, like size variation and the new stars.&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Thu, 21 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2023/12/upgrading-the-star-and-planet-generators-on-iron-arachne/</guid></item><item><title>Two Toucans Canoe</title><link>https://taylor.town/two-toucans-canoe</link><description>Toucan Dan commands, "Paddle faster, Anne!"</description><author>taylor.town</author><pubDate>Thu, 21 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/two-toucans-canoe</guid></item><item><title>"We get good at what we do."</title><link>https://bowendwelle.substack.com/p/we-get-good-at-what-we-do</link><description>Bowen Dwelle in conversation with Donna McArthur of The Bright Life</description><author>An Ordinary Disaster</author><pubDate>Wed, 20 Dec 2023 20:01:03 GMT</pubDate><guid isPermaLink="true">https://bowendwelle.substack.com/p/we-get-good-at-what-we-do</guid></item><item><title>Back to the Mac</title><link>https://blog.herlein.com/post/back-to-mac/</link><description>&lt;p&gt;I undid it.  I&amp;rsquo;m still using my desktop (mostly remotely) and I do love my Lenovo X1 Carbon.  But my primary machine is back to the Mac Book Pro.&lt;/p&gt;</description><author>Greg Herlein</author><pubDate>Wed, 20 Dec 2023 18:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.herlein.com/post/back-to-mac/</guid></item><item><title>RAG, or how to talk to our documents?</title><link>https://kaszkowiak.org/en/blog/retrieval-augmented-generation/</link><description>Discover how Retrieval Augmented Generation enables large language models (LLMs) to provide accurate answers based on expertise.</description><author>Blog on Maciej Kaszkowiak</author><pubDate>Wed, 20 Dec 2023 15:00:00 GMT</pubDate><guid isPermaLink="true">https://kaszkowiak.org/en/blog/retrieval-augmented-generation/</guid></item><item><title>Facebook again?  Maybe?  But they still suck</title><link>https://blog.herlein.com/post/facebook-again/</link><description>I left Facebook to get away from all the hate. But there&amp;rsquo;s community information there I find valuable and so I signed up again (after years away). And my account is suspended!
Somehow I didn&amp;rsquo;t follow their community standards EVEN BEFORE I LOGGED IN! I can tell you that all I ever did was leave Facebook and delete my account there. I certainly never &amp;ldquo;violated thier community standards&amp;rdquo; (as if they really have them with the hate they promote).</description><author>Greg Herlein</author><pubDate>Wed, 20 Dec 2023 10:00:01 GMT</pubDate><guid isPermaLink="true">https://blog.herlein.com/post/facebook-again/</guid></item><item><title>How To Read An Article On The Internet</title><link>https://www.marginalia.nu/log/95_how_to_read/</link><description>&lt;pre&gt;&lt;code&gt; A simple guide to reading
 in 9 simple steps
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Navigate to the desired article.&lt;/li&gt;
&lt;li&gt;Dismiss the GDPR banner&lt;/li&gt;
&lt;li&gt;It may seem safe to start reading, but you need to wait about 10 seconds as the
various ad auctions resolve and scripts load in&lt;/li&gt;
&lt;li&gt;Wait while the article is populated with ads. While the article is in front of you, there is no point to
starting to read yet, as the minute&amp;rsquo;s worth of layout shift will make you lose your place.&lt;/li&gt;
&lt;li&gt;Once the layout has settled and not shifted for 10 seconds, move the mouse cursor toward the URL bar to
trigger any sort of exit intent scripts you might otherwise accidentally invoke while reading.&lt;/li&gt;
&lt;li&gt;Scroll to the end of the article to ensure a paywall doesn&amp;rsquo;t appear halfway through the article.&lt;/li&gt;
&lt;li&gt;Dismiss any calls to subscribe to a newsletter that may have appeared in step 6.&lt;/li&gt;
&lt;li&gt;Press Ctrl+F and search for out-of-place paragraphs beginning with &amp;ldquo;Benefits of&amp;rdquo;; this
is a strong indicator the article is some ChatGPT content farm gibberish.&lt;/li&gt;
&lt;li&gt;Press Ctrl+P to bring up the print preview dialog and read the article in print preview&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Some also say you should press the back button once to get any back button hijackings out of the way,
but your mileage vay vary.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Wed, 20 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/95_how_to_read/</guid></item><item><title>WARC'in the crawler</title><link>https://www.marginalia.nu/log/94_warc_warc/</link><description>&lt;p&gt;The Marginalia Crawler has seen improvements! A long term problem with the crawler design is
that if for whatever reason the crawler shuts down, then it needs to re-start fetching whatever
domains it was currently traversing during the termination from zero.&lt;/p&gt;
&lt;p&gt;This isn&amp;rsquo;t fantastic, since not only does crawling a website take a fair bit of time,
it&amp;rsquo;s a nuisance for the server admins to re-crawl stuff that was already fetched, and
a real liability for ending up in robots.txt or some iptables ruleset.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Wed, 20 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/94_warc_warc/</guid></item><item><title>The Paradox of Freedom</title><link>https://backendhance.com/blog/2023/the-paradox-of-freedom/</link><description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Paradox of Freedom: The more options we have, the more anxious we become that we chose the wrong thing.&lt;/p&gt;
&lt;p&gt;~ Mark Manson&lt;/p&gt;&lt;/blockquote&gt;
&lt;h1 id="mark-manson-ist-kein-softwareentwickler-er-ist-autor-sein-bestseller-the-subtle-art-of-not-giving-a-fck"&gt;
&lt;a class="anchor" href="#mark-manson-ist-kein-softwareentwickler-er-ist-autor-sein-bestseller-the-subtle-art-of-not-giving-a-fck"&gt;Mark Manson ist kein Softwareentwickler. Er ist Autor. Sein Bestseller: &amp;ldquo;THE SUBTLE ART OF NOT GIVING A F*CK&amp;rdquo;&lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;Er gibt Selbsthilfe für Menschen, die Selbsthilfe Bücher hassen. In dem Paradoxon bezieht er sich auf Lebensentscheidungen. Beispielsweise fällt es vielen Menschen schwer eine Liebesbeziehung über Tinder einzugehen. Die Optionen sind so groß, dass man sich bei jedem Date denkt, man könnte noch was besseres haben.&lt;/p&gt;</description><author>Backendhance</author><pubDate>Wed, 20 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/the-paradox-of-freedom/</guid></item><item><title>Town Hall #18: Sunflower</title><link>https://taylor.town/town-hall-0018</link><description>Evergreens and oaks sprouted in unwanted places. Those trees have unwittingly become bonsai, confined to makeshift plastic pots.</description><author>taylor.town</author><pubDate>Wed, 20 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/town-hall-0018</guid></item><item><title>S3 Batch Operations - Lambda</title><link>https://www.vladsiv.com/posts/s3-batch-operations-lambda</link><description>A brief guide outlining the process of setting up and running S3 Batch Operations Jobs with Lambda integration.</description><author>vladsiv</author><pubDate>Wed, 20 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.vladsiv.com/posts/s3-batch-operations-lambda</guid></item><item><title>Functional Languages Need Not Be Slow</title><link>https://lambdaland.org/posts/2023-12-20_functional_langauge_speed/</link><description>&lt;p&gt;Somewhere in my adolescence I got stuck with the notion that functional languages were slow while languages like C were fast. Now, a good C programmer can eke more performance out of their code than probably anyone else, but the cost you pay to keep your code correct goes exponential as you get closer and closer to the machine.&lt;/p&gt;
&lt;p&gt;Functional languages abstract a lot away from the machine. Higher languages in general abstract away the machine and make code easier to maintain. So I think I had it in my head that functional languages, being far away from the bare metal, must necessarily be slow. It didn’t help that I also thought of functional languages as being necessarily interpreted languages.&lt;/p&gt;
&lt;p&gt;Turns out, functional languages are just as amenable to compilation as imperative ones. Many popular/well-known functional are in fact compiled. (E.g. Haskell, Scala, Rust, Julia, etc.) Moreover, these languages can be just as fast—if not faster—than their more “mainstream” counterparts.&lt;/p&gt;
&lt;p&gt;I wanted to pit my favorite language (Racket) against a &lt;em&gt;slightly&lt;/em&gt; more well-known language (Python) to see how they handled a simple single-threaded program. For good measure I threw Rust, Julia, and JavaScript into the mix for comparison.&lt;/p&gt;
&lt;p&gt;If you’re impatient, just jump to &lt;a href="https://lambdaland.org/posts/2023-12-20_functional_langauge_speed/#the-results"&gt;the results&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="the-programs"&gt;The programs&lt;/h2&gt;
&lt;p&gt;I wrote the original program in Racket, then had ChatGPT help me rewrite it in Python. ChatGPT did astoundingly well. I eventually rewrote the program to be a little more idiomatic—I wanted it to use a loop instead of tail recursion, as Python is much better with loops than it is with lots and lots of function calls. I also had ChatGPT help me rewrite the program to Rust, Julia, and JavaScript. Impressive—and unsettling.&lt;/p&gt;
&lt;pre class="giallo" style="color: #D8DEE9; background-color: #2E3440;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;#lang&lt;/span&gt;&lt;span&gt; racket&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;define&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;count-collatz&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; [&lt;/span&gt;&lt;span&gt;cnt&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;  (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;cond&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;    [(&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;=&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;span&gt; cnt&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;    [(&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;even?&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;) (&lt;/span&gt;&lt;span&gt;count-collatz&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;/&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 2&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;) (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;+&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span&gt; cnt&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;))]&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;    [&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;else&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;count-collatz&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;+&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;*&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 3&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;) (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;+&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span&gt; cnt&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;))]))&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;define&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;count-collatz-upto&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;  (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;for/fold&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;      ([&lt;/span&gt;&lt;span&gt;max-seen&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 0&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;      ([&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;in-range&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)])&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;    (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;max&lt;/span&gt;&lt;span&gt; max-seen&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;count-collatz i&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;))))&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;displayln&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;format&lt;/span&gt;&lt;span style="color: #A3BE8C;"&gt; &amp;quot;&lt;/span&gt;&lt;span style="color: #EBCB8B;"&gt;\n&lt;/span&gt;&lt;span style="color: #A3BE8C;"&gt;Done ~a&amp;quot;&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;count-collatz-upto&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 5000000&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="src-block-caption"&gt;
  &lt;span class="src-block-number"&gt;Code Snippet 1:&lt;/span&gt;
  &lt;code&gt;collatz_check.rkt&lt;/code&gt;
&lt;/div&gt;
&lt;pre class="giallo" style="color: #D8DEE9; background-color: #2E3440;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;def&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; count_collatz&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    cnt&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; =&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;    while&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; !=&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;        if&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; %&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 2&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; ==&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 0&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;            n&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; =&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; //&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 2&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;            cnt&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; +=&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;        else&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;            n&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; =&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 3&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; *&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; +&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;            cnt&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; +=&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;    return&lt;/span&gt;&lt;span&gt; cnt&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;max_seen&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; =&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;def&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; count_collatz_upto&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;    global&lt;/span&gt;&lt;span&gt; max_seen&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;    for&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; in&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; range&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt;1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;,&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;        the_count&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; =&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; count_collatz&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;        max_seen&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; =&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; max&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span&gt;max_seen&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;,&lt;/span&gt;&lt;span&gt; the_count&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;    return&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 42&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #88C0D0;"&gt;count_collatz_upto&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt;5000000&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #88C0D0;"&gt;print&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&amp;quot;&lt;/span&gt;&lt;span style="color: #EBCB8B;"&gt;\n&lt;/span&gt;&lt;span style="color: #A3BE8C;"&gt;Done&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;&amp;quot;,&lt;/span&gt;&lt;span&gt; max_seen&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="src-block-caption"&gt;
  &lt;span class="src-block-number"&gt;Code Snippet 2:&lt;/span&gt;
  &lt;code&gt;collatz_check.py&lt;/code&gt;
&lt;/div&gt;
&lt;pre class="giallo" style="color: #D8DEE9; background-color: #2E3440;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;fn&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; count_collatz&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;:&lt;/span&gt;&lt;span style="color: #8FBCBB;"&gt; u64&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; -&amp;gt;&lt;/span&gt;&lt;span style="color: #8FBCBB;"&gt; u64&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;    let mut&lt;/span&gt;&lt;span&gt; cnt&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; =&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;    let mut&lt;/span&gt;&lt;span&gt; num&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; =&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;    while&lt;/span&gt;&lt;span&gt; num&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; !=&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;        if&lt;/span&gt;&lt;span&gt; num&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; %&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 2&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; ==&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 0&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;            num&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; /=&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 2&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;        }&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; else&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;            num&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; =&lt;/span&gt;&lt;span&gt; num&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; *&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 3&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; +&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;        cnt&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; +=&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    cnt&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;fn&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; count_collatz_upto&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;:&lt;/span&gt;&lt;span style="color: #8FBCBB;"&gt; u64&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; -&amp;gt;&lt;/span&gt;&lt;span style="color: #8FBCBB;"&gt; u64&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;    let mut&lt;/span&gt;&lt;span&gt; max_seen&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; =&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 0&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;    for&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; in&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;..&lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;        max_seen&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; =&lt;/span&gt;&lt;span&gt; max_seen&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;.&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;max&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;count_collatz&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    max_seen&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;fn&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; main&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #88C0D0; font-weight: bold;"&gt;    println!&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&amp;quot;&lt;/span&gt;&lt;span style="color: #A3BE8C;"&gt;Done &lt;/span&gt;&lt;span style="color: #EBCB8B;"&gt;{}&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;&amp;quot;,&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; count_collatz_upto&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt;5000000&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="src-block-caption"&gt;
  &lt;span class="src-block-number"&gt;Code Snippet 3:&lt;/span&gt;
  &lt;code&gt;collatz_check.rs&lt;/code&gt;
&lt;/div&gt;
&lt;pre class="giallo" style="color: #D8DEE9; background-color: #2E3440;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;function&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; count_collatz&lt;/span&gt;&lt;span&gt;(n&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;::&lt;/span&gt;&lt;span style="color: #8FBCBB;"&gt;Int&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    cnt &lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;=&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;    while&lt;/span&gt;&lt;span&gt; n &lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;!=&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;        if&lt;/span&gt;&lt;span&gt; n &lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;%&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 2&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; ==&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;            n &lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;=&lt;/span&gt;&lt;span&gt; n &lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;÷&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 2&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;            cnt &lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;+=&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;        else&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;            n &lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;=&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 3&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; *&lt;/span&gt;&lt;span&gt; n &lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;+&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;            cnt &lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;+=&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;        end&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;    return&lt;/span&gt;&lt;span&gt; cnt&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;max_seen &lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;=&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;function&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; count_collatz_upto&lt;/span&gt;&lt;span&gt;(n&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;::&lt;/span&gt;&lt;span style="color: #8FBCBB;"&gt;Int&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;    for&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;in&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;:&lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;-&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;        the_count &lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;=&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; count_collatz&lt;/span&gt;&lt;span&gt;(i)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;        global&lt;/span&gt;&lt;span&gt; max_seen &lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;=&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; max&lt;/span&gt;&lt;span&gt;(max_seen&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;,&lt;/span&gt;&lt;span&gt; the_count)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;    return&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 42&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #88C0D0;"&gt;count_collatz_upto&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt;5000000&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #88C0D0;"&gt;println&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #EBCB8B;"&gt;\n&lt;/span&gt;&lt;span style="color: #A3BE8C;"&gt;Done &lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;&amp;quot;,&lt;/span&gt;&lt;span&gt; max_seen)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="src-block-caption"&gt;
  &lt;span class="src-block-number"&gt;Code Snippet 4:&lt;/span&gt;
  &lt;code&gt;collatz_check.jl&lt;/code&gt;
&lt;/div&gt;
&lt;pre class="giallo" style="color: #D8DEE9; background-color: #2E3440;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;function&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; countCollatz&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;  let&lt;/span&gt;&lt;span&gt; cnt&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; =&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;  while&lt;/span&gt;&lt;span&gt; (n&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; !==&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;    if&lt;/span&gt;&lt;span&gt; (n&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; %&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 2&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; ===&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;      n&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; =&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; /&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 2&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;    }&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; else&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;      n&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; =&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 3&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; *&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; +&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    cnt&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;++;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;  return&lt;/span&gt;&lt;span&gt; cnt&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;function&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; countCollatzUpto&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;  let&lt;/span&gt;&lt;span&gt; maxSeen&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; =&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 0&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;  for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;let&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; =&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;;&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; &amp;lt;&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;;&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;++&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    maxSeen&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; =&lt;/span&gt;&lt;span&gt; Math&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;.&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;max&lt;/span&gt;&lt;span&gt;(maxSeen&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;,&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; countCollatz&lt;/span&gt;&lt;span&gt;(i))&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;  return&lt;/span&gt;&lt;span&gt; maxSeen&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;console&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;.&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;`&lt;/span&gt;&lt;span style="color: #EBCB8B;"&gt;\n&lt;/span&gt;&lt;span style="color: #A3BE8C;"&gt;Done &lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;${&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;countCollatzUpto&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt;5000000&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;}&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="src-block-caption"&gt;
  &lt;span class="src-block-number"&gt;Code Snippet 5:&lt;/span&gt;
  &lt;code&gt;collatz_check.js&lt;/code&gt;
&lt;/div&gt;
&lt;h2 id="the-results"&gt;The results&lt;/h2&gt;
&lt;p&gt;I ran these programs on my M1 Pro MacBook Pro. Here’s what I got:&lt;/p&gt;

&lt;div class="ox-hugo-table table-nocaption"&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Language&lt;/th&gt;&lt;th&gt;Time (seconds)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Racket&lt;/td&gt;&lt;td&gt;4.68&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Python&lt;/td&gt;&lt;td&gt;34.27&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Rust&lt;/td&gt;&lt;td&gt;3.03&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Julia&lt;/td&gt;&lt;td&gt;2.34&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;JavaScript&lt;/td&gt;&lt;td&gt;11.92&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;In graphical form:&lt;/p&gt;
&lt;div class="figure"&gt;
  &lt;p&gt;&lt;img alt="" src="https://lambdaland.org/img/benchmark_shootout.svg" /&gt;&lt;/p&gt;

  
&lt;/div&gt;
&lt;p&gt;Wow! I did not expect Python to get so pummeled by everything else! It makes sense that Julia with its sweet JIT engine is the fastest. Rust does well—no surprise there. (Note that I’m not counting compilation time here—all the more impressive for Julia!) Racket holds its own though, coming in third place by a wide margin. If you did take Rust’s compile time into account, I think that would make it switch places with Racket. Of course, you use Rust for compile-once-run-lots sorts of scenarios.&lt;/p&gt;
&lt;h2 id="discussion"&gt;Discussion&lt;/h2&gt;
&lt;p&gt;Are these authoritative? No, of course not. This is a simple synthetic benchmark. I don’t consider myself expert programmer in &lt;em&gt;any&lt;/em&gt; of these languages, so there are likely some performance tweaks that could make the respective language’s benchmark run faster. (Maybe I should in Racket… it’s kind of hard to consider yourself an expert in a language when its creator works down the hall from you though.)&lt;/p&gt;
&lt;p&gt;That said, I hope this dispels the myth that functional languages are &lt;em&gt;necessarily&lt;/em&gt; slow. That is not the case. If Python is fast enough for your use-case, then there’s no reason you shouldn’t consider using Racket on performance grounds.&lt;/p&gt;
&lt;p&gt;Library support is a different matter: of all the programming that goes on in the world, the majority is probably just gluing libraries together to do the thing you want. This is a good thing: it means we’re not reinventing so many wheels and people are getting good work done.&lt;/p&gt;
&lt;p&gt;That said, there’s plenty of exciting work for which no library exists! If you find yourself in one of these exciting cases, consider using the tool of maximum power: in this regard, nothing beats Racket for its flexibility and &lt;a href="https://lambdaland.org/posts/2023-10-17_fearless_macros/"&gt;extensibility&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="bonus-building-the-graph-in-racket"&gt;Bonus: building the graph in Racket&lt;/h2&gt;
&lt;p&gt;I love the Racket &lt;a class="link-external" href="https://docs.racket-lang.org/plot/" rel="external"&gt;Plot&lt;/a&gt; library. So easy to use, so powerful. If you run it inside of DrRacket, the graphs are actually interactive: you can rotate 3D graphs and zoom in on sections of 2D graphs. So neat!&lt;/p&gt;
&lt;p&gt;Here’s the code I used to generate the above graph:&lt;/p&gt;
&lt;pre class="giallo" style="color: #D8DEE9; background-color: #2E3440;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;#lang&lt;/span&gt;&lt;span&gt; racket&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;require&lt;/span&gt;&lt;span&gt; plot&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;define&lt;/span&gt;&lt;span&gt; *data*&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;list&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; #(&lt;/span&gt;&lt;span style="color: #A3BE8C;"&gt;&amp;quot;Racket&amp;quot;&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 4.68&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;) #(&lt;/span&gt;&lt;span style="color: #A3BE8C;"&gt;&amp;quot;Python&amp;quot;&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 34.27&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;) #(&lt;/span&gt;&lt;span style="color: #A3BE8C;"&gt;&amp;quot;Rust&amp;quot;&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 3.03&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;) #(&lt;/span&gt;&lt;span style="color: #A3BE8C;"&gt;&amp;quot;Julia&amp;quot;&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 2.34&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;) #(&lt;/span&gt;&lt;span style="color: #A3BE8C;"&gt;&amp;quot;JavaScript&amp;quot;&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 11.92&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)))&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;parameterize&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; ([&lt;/span&gt;&lt;span&gt;plot-font-face&lt;/span&gt;&lt;span style="color: #A3BE8C;"&gt; &amp;quot;Charter&amp;quot;&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;               [&lt;/span&gt;&lt;span&gt;plot-font-size&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 14.0&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;               [&lt;/span&gt;&lt;span&gt;plot-x-ticks&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;linear-ticks&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; #:number&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 10&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; #:divisors&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; '&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt;1 2 5&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;))])&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;  (&lt;/span&gt;&lt;span&gt;plot&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;list&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;discrete-histogram&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;reverse&lt;/span&gt;&lt;span&gt; *data*&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;                                  #:gap&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 0.3&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;                                  #:invert? #true&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;        #:title&lt;/span&gt;&lt;span style="color: #A3BE8C;"&gt; &amp;quot;Simple benchmark results&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;        #:out-kind&lt;/span&gt;&lt;span style="color: #A3BE8C;"&gt; 'svg&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;        #:out-file&lt;/span&gt;&lt;span style="color: #A3BE8C;"&gt; &amp;quot;benchmark_shootout.svg&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;        #:x-max&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 35&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;        #:x-label&lt;/span&gt;&lt;span style="color: #A3BE8C;"&gt; &amp;quot;Seconds&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;        #:width&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 800&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;        #:height&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 200&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</description><author>Lambda Land</author><pubDate>Wed, 20 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://lambdaland.org/posts/2023-12-20_functional_langauge_speed/</guid></item><item><title>Scenic II Lüftungsgebläse plötzlich aus/Vorwiderstand defekt – die Zweite</title><link>https://autotagebuch.net/scenic-ii-lueftungsgeblaese-ploetzlich-aus-vorwiderstand-defekt-die-zweite/</link><description>&lt;p&gt;Mitte 2015 war bei unserem Scenic II plötzlich das Lüftungsgebläse aus. Seinerzeit berichtete ich im Beitrag &amp;#8222;Scenic II Lüftungsgebläse plötzlich aus&amp;#8222;, wie eine freie Werkstatt diesen gegen ein originales Ersatzteil vom Erstausrüster (Valeo) tauschte. Kostenpunkt damals: 172 Euro. Den genauen Defekt am Vorwiderstand erfuhren wir damals nicht. Nun, fast pünktlich zu Weihnachten, war die Lüftung [&amp;#8230;]&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://autotagebuch.net/scenic-ii-lueftungsgeblaese-ploetzlich-aus-vorwiderstand-defekt-die-zweite/"&gt;Scenic II Lüftungsgebläse plötzlich aus/Vorwiderstand defekt &amp;#8211; die Zweite&lt;/a&gt; appeared first on &lt;a href="https://autotagebuch.net"&gt;Das Autotagebuch&lt;/a&gt;.&lt;/p&gt;</description><author>Das Autotagebuch</author><pubDate>Wed, 20 Dec 2023 00:26:48 GMT</pubDate><guid isPermaLink="true">https://autotagebuch.net/scenic-ii-lueftungsgeblaese-ploetzlich-aus-vorwiderstand-defekt-die-zweite/</guid></item><item><title>The books I enjoyed the most in 2023</title><link>https://blog.ovalerio.net/archives/2706</link><description>We reached the end of another year, and generally, this is a good time to look back, to evaluate what was done, what wasn&amp;#8217;t done and eventually plan ahead. While dedicating some time to the first task, it occurred to me that I should share some of this stuff. I doubt it will be useful [&amp;#8230;]</description><author>Gonçalo Valério</author><pubDate>Tue, 19 Dec 2023 21:50:32 GMT</pubDate><guid isPermaLink="true">https://blog.ovalerio.net/archives/2706</guid></item><item><title>La publicité pour les trains</title><link>https://ache.one/articles/les-trains-et-la-publicit%C3%A9</link><description>Parlons trains. Enfin pub.
Enfin ! Vous verrez bien !
L'idée est d'analyser quelques publicités pour des trains à travers le monde.
Mais c'est surtout un prétexte pour vous présenter l'utilisation de technologies d'intelligence artificielle récentes. 🤖</description><author>ache: Blog personnel</author><pubDate>Tue, 19 Dec 2023 15:34:00 GMT</pubDate><guid isPermaLink="true">https://ache.one/articles/les-trains-et-la-publicit%C3%A9</guid></item><item><title>My cat water fountain comes with a spicy USB power adapter</title><link>https://ounapuu.ee/posts/2023/12/19/spicy-usb-adapter/</link><description>It turns out that you can't trust any USB type A power adapter to be within spec.</description><author>./techtipsy</author><pubDate>Tue, 19 Dec 2023 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ounapuu.ee/posts/2023/12/19/spicy-usb-adapter/</guid></item><item><title>For All Mankind: Season 2</title><link>https://olshansky.info/tv/for_all_mankind_season_2/</link><description>Olshansky's review of For All Mankind: Season 2</description><author>🦉 olshansky 🦁</author><pubDate>Tue, 19 Dec 2023 03:18:01 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/tv/for_all_mankind_season_2/</guid></item><item><title>The "Cheap" Web</title><link>https://potato.cheap</link><description>The "cheap" web is a solarpunk philosophy of web design.</description><author>taylor.town</author><pubDate>Tue, 19 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://potato.cheap</guid></item><item><title>WishWell.io</title><link>https://wishwell.io</link><description>WishWell is a simple patronage service.</description><author>taylor.town</author><pubDate>Tue, 19 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://wishwell.io</guid></item><item><title>Blazor on Netlify With Environment Variables!</title><link>https://chrisfrew.in/blog/blazor-on-netlify-with-environment-variables/</link><description>Make one of Microsoft's newer technologies configurable!</description><author>Chris' Full Stack Blog RSS Feed</author><pubDate>Tue, 19 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://chrisfrew.in/blog/blazor-on-netlify-with-environment-variables/</guid></item><item><title>Logical Replication From Postgres 16 Stand-By Servers—Debezium and Failover Slots</title><link>https://www.morling.dev/blog/logical-replication-from-postgres-stand-by-servers-debezium-and-failover-slots/</link><description>&lt;div class="toc" id="toc"&gt;
&lt;div id="toctitle"&gt;Table of Contents&lt;/div&gt;
&lt;ul class="sectlevel1"&gt;
&lt;li&gt;&lt;a href="#_stand_by_logical_replication_with_debezium"&gt;Stand-By Logical Replication With Debezium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_towards_fail_over_slots"&gt;Towards Fail-Over Slots&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_wrap_up"&gt;Wrap-Up&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;em&gt;This post originally appeared on the &lt;a href="https://www.decodable.co/blog/logical-replication-from-postgres-16-stand-by-servers-part-2-of-2"&gt;Decodable blog&lt;/a&gt;. All rights reserved.&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Welcome back to this series about logical replication from Postgres 16 stand-by servers, in which we’ll discuss how to use this feature with Debezium—a popular open-source platform for Change Data Capture (CDC) for a wide range of databases—as well as how to manage logical replication in case of failover scenarios, i.e.
a situation where your primary Postgres server becomes unavailable and a stand-by server needs to take over.&lt;/p&gt;
&lt;/div&gt;</description><author>Gunnar Morling</author><pubDate>Tue, 19 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.morling.dev/blog/logical-replication-from-postgres-stand-by-servers-debezium-and-failover-slots/</guid></item><item><title>Using Stand-by Servers for Postgres Logical Replication</title><link>https://www.morling.dev/blog/stand-by-servers-for-postgres-logical-replication/</link><description>&lt;div class="toc" id="toc"&gt;
&lt;div id="toctitle"&gt;Table of Contents&lt;/div&gt;
&lt;ul class="sectlevel1"&gt;
&lt;li&gt;&lt;a href="#_what_is_postgres_logical_replication"&gt;What is Postgres Logical Replication?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_why_logical_replication_on_stand_by_servers"&gt;Why Logical Replication On Stand-By Servers?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_provisioning_a_testing_environment"&gt;Provisioning a Testing Environment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_testing_things_out"&gt;Testing Things Out&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;em&gt;This post originally appeared on the &lt;a href="https://www.decodable.co/blog/postgres-logical-replication"&gt;Decodable blog&lt;/a&gt;. All rights reserved.&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;For users of Change Data Capture (CDC), one of the most exciting features in Postgres version 16 (released in September this year) is the support for logical replication from stand-by servers.
Instead of connecting to your primary server, you can now point CDC tools such as Debezium to a replica server, which is very interesting for instance from a load distribution perspective.
I am going to take a closer look at this new feature in this two-part blog series:&lt;/p&gt;
&lt;/div&gt;</description><author>Gunnar Morling</author><pubDate>Tue, 19 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.morling.dev/blog/stand-by-servers-for-postgres-logical-replication/</guid></item><item><title>The "Dont" Tool</title><link>https://synystron.substack.com/p/the-dont-tool</link><description>A proposal for a new Unix-style CLI tool</description><author>Synystron Synlogica</author><pubDate>Mon, 18 Dec 2023 21:08:50 GMT</pubDate><guid isPermaLink="true">https://synystron.substack.com/p/the-dont-tool</guid></item><item><title>Useless Things You Remember</title><link>https://ilearnt.com/blog/uselessthingsyouremember/</link><description>&lt;p&gt;During a team call this morning, one of the engineers mentioned about the useless things he still remembers and how it would be good if the brain would clear them out.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Mon, 18 Dec 2023 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/uselessthingsyouremember/</guid></item><item><title>Describing The Bureau of Magical Things Season 2</title><link>https://brett.coulstock.id.au/bureau-of-magical-things-audio-description.html</link><description>Audio description scripts for selected episodes of The Bureau of Magical Things, Season 2</description><author>Brett Coulstock - Writer</author><pubDate>Mon, 18 Dec 2023 11:00:00 GMT</pubDate><guid isPermaLink="true">https://brett.coulstock.id.au/bureau-of-magical-things-audio-description.html</guid></item><item><title>Pacifica After Rain</title><link>https://www.hypergeometric.com/2023/12/18/pacifica-after-rain/</link><description>&lt;p&gt;A brief moment waiting for my little one’s swim lesson to start.&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://www.hypergeometric.com/2023/12/18/pacifica-after-rain/"&gt;&lt;span class="p-name"&gt;Pacifica After Rain&lt;/span&gt;&lt;/a&gt; first appeared on &lt;a href="https://www.hypergeometric.com"&gt;hypergeometric&lt;/a&gt;.&lt;/p&gt;</description><author>hypergeometric</author><pubDate>Mon, 18 Dec 2023 02:16:00 GMT</pubDate><guid isPermaLink="true">https://www.hypergeometric.com/2023/12/18/pacifica-after-rain/</guid></item><item><title>Why do programmers need private offices with doors?</title><link>https://studiofreya.org/reddit/why-do-programmers-need-private-offices-with-doors/</link><description>&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://www.reddit.com/r/programming/comments/18l88xq/why_do_programmers_need_private_offices_with/"&gt;https://www.reddit.com/r/programming/comments/18l88xq/why_do_programmers_need_private_offices_with/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;</description><author>Studiofreya SSG Site</author><pubDate>Mon, 18 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://studiofreya.org/reddit/why-do-programmers-need-private-offices-with-doors/</guid></item><item><title>Scientific Freedom: The Elixir of Civilization</title><link>https://olshansky.info/book/scientific_freedom/</link><description>Olshansky's review of Scientific Freedom: The Elixir of Civilization</description><author>🦉 olshansky 🦁</author><pubDate>Mon, 18 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/book/scientific_freedom/</guid></item><item><title>Lambda Warming is an Antipattern</title><link>/lambda-warming-antipattern/</link><description>&lt;p&gt;At a certain stage during most monolith to microservice migrations, teams that choose to migrate to Lambda often encounter a classic problem: cold starts.&lt;/p&gt;
&lt;p&gt;Searching this issue on Google (or DuckDuckGo for those conscious of privacy) yields thousands of results. Strategies abound, from reducing cold start times to using Lambda warmers and other seemingly haphazard tactics.&lt;/p&gt;
&lt;p&gt;On the surface, optimizing your Lambdas seems beneficial. Lambda bills per millisecond of compute time, so optimizing at scale can lead to marginal cost savings, not to mention the customer impact of a faster app.&lt;/p&gt;
&lt;p&gt;I won't rehash the numerous articles suggesting sound methods to improve Lambda performance, such as increasing memory, avoiding placement in VPCs, and choosing quick languages like Python, Go, or Node.js (in that order).&lt;/p&gt;
&lt;p&gt;Instead, I'll focus on another strategy often suggested to mitigate cold start problems: Lambda warming.&lt;/p&gt;
&lt;p&gt;Lambda warming is a method where Lambdas are pinged to maintain an active, spun-up instance. Theoretically, this eliminates the time AWS takes to boot the container. However, as Yan Cui points out on The Burning Monk, this assumption is flawed. By implementing a Lambda warming mechanism, you're only keeping a single instance of your app alive (until it recycles about every 45 minutes, a built-in AWS quirk) rather than having many concurrent instances ready for traffic.&lt;/p&gt;
&lt;p&gt;Beyond this apparent flaw, Lambda warming disrupts the pattern of Lambda's transactional, functional nature—stateless input and output.&lt;/p&gt;
&lt;p&gt;If you've concluded that you need Lambda warming, it suggests a larger issue: the need for low latency in your backend systems, either for customer experience or technical reasons. And by using Lambda warming, you're likely willing to invest money to combat 300ms of latency.&lt;/p&gt;
&lt;p&gt;If these statements ring true, then the reality is you might be better served by a different service like ECS Fargate, Kubernetes (EKS), or traditional EC2—essentially, anything long-lived.&lt;/p&gt;
&lt;p&gt;Lambda is a fantastic tool, but it's not a one-size-fits-all solution. Often, a HTTP API with some cold start latency is perfectly acceptable. However, for real-time services like chat, it might not be the best fit. Yet, if your traffic for such a service is consistent, then perhaps it is suitable!&lt;/p&gt;
&lt;p&gt;The key is to gather data, understand your use case and pain points, and then evaluate your options. Don't rush to a workaround without thorough consideration.&lt;/p&gt;</description><author/><pubDate>Mon, 18 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">/lambda-warming-antipattern/</guid></item><item><title>Three days of Advent of Code in Hurl</title><link>https://ntietz.com/blog/three-days-of-advent-of-code-in-hurl/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;Every year I do some of &lt;a href="https://adventofcode.com/"&gt;Advent of Code&lt;/a&gt;.
One year I completed it, but usually I just do some of it as a social thing with friends and then taper off as interest wanes.
This year, I did three days of it, and stopped because I really truly did not want to write more solutions in the language I chose.&lt;/p&gt;
&lt;p&gt;See, previous years I made a reasonable choice, like &lt;a href="https://github.com/ntietz/advent-of-code-2021"&gt;Rust&lt;/a&gt;.
But this year, since I &lt;a href="https://ntietz.com/blog/lessons-from-implementing-hurl/"&gt;wrote a programming language&lt;/a&gt;, I decided to do at least three days of Advent of Code in it, and more if I wanted to.
(Dear reader, I did not want to.)&lt;/p&gt;
&lt;p&gt;These three days of it were very useful in getting comfortable with Hurl and they were also critical in developing Hurl's built-ins and standard library to a reasonable point.
I'm pretty confident now that you could do all of Advent of Code in it.
And I'm also now &lt;em&gt;free&lt;/em&gt; from Hurl, and so are you: this is the last either of us need to think about it&lt;sup class="footnote-reference" id="fr-maybe-1"&gt;&lt;a href="https://ntietz.com/blog/three-days-of-advent-of-code-in-hurl/#fn-maybe"&gt;[1]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;If you want to see all the solutions I've implemented, they're &lt;a href="https://git.sr.ht/~ntietz/hurl-lang/tree/main/item/aoc"&gt;in Hurl's repo&lt;/a&gt; as examples and tests&lt;sup class="footnote-reference" id="fr-1-1"&gt;&lt;a href="https://ntietz.com/blog/three-days-of-advent-of-code-in-hurl/#fn-1"&gt;[2]&lt;/a&gt;&lt;/sup&gt;.
For now, I'll walk through &lt;em&gt;one&lt;/em&gt; solution and how it works.&lt;/p&gt;
&lt;h1 id="deep-dive-of-day-1-part-2"&gt;Deep dive of day 1, part 2&lt;/h1&gt;
&lt;p&gt;Day 1 part 2 had some interesting flair to it where it is short but also interesting enough.
The premise is that you have a document where each line contains characters and you're trying to find the numbers on each line.
You take the first and last single-digit number, then concatenate them together, then sum all such numbers from all the lines.
But they also might be written &lt;em&gt;as English words&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Here's a short example of a document.&lt;/p&gt;
&lt;pre class="language-text " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-text"&gt;&lt;span&gt;zoneight234
&lt;/span&gt;&lt;span&gt;7pqrstsixteen
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From the first line we get "one" and "4", so that becomes 14, and the second line gives us "7" and "six", which becomes 76.
The solution for this document then is 14 + 76 = 90.&lt;/p&gt;
&lt;p&gt;The first thing we do is import the standard library functionality we will need.
The paths here are relative to the source file you run, so they may change for different users.&lt;/p&gt;
&lt;pre class="language-hurl " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-hurl"&gt;&lt;span&gt;include &amp;quot;../lib/loop.hurl&amp;quot;;
&lt;/span&gt;&lt;span&gt;include &amp;quot;../lib/if.hurl&amp;quot;;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we can read in our input.
We use built-ins for reading the file's input and breaking it into lines.
Breaking it into lines could have been done in Hurl, but I made it a built-in to save time.&lt;/p&gt;
&lt;pre class="language-hurl " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-hurl"&gt;&lt;span&gt;let input = read_file(&amp;quot;./aoc/input/day1.txt&amp;quot;);
&lt;/span&gt;&lt;span&gt;let lines = str_lines(input);
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can iterate over the lines and extract the solution, once we define the &lt;code&gt;extract_nums&lt;/code&gt; function.&lt;/p&gt;
&lt;pre class="language-hurl " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-hurl"&gt;&lt;span&gt;let total = 0;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;for_each(lines, func(line) {
&lt;/span&gt;&lt;span&gt;    try {
&lt;/span&gt;&lt;span&gt;        extract_nums(line);
&lt;/span&gt;&lt;span&gt;    } catch as val {
&lt;/span&gt;&lt;span&gt;        total = total + val;
&lt;/span&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;span&gt;});
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;println(&amp;quot;solution: &amp;quot;, total);
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;for_each&lt;/code&gt; here is defined in Hurl's standard library, only using Hurl itself (exceptions and recursion).
It accepts a list or string as its first argument and a function to call on each element as its second argument.
Inside of there, we use a try-catch to get the value of each line via the &lt;code&gt;extract_nums&lt;/code&gt; function and add it into the running total, which we'll then print out.&lt;/p&gt;
&lt;p&gt;Now let's define &lt;code&gt;extact_nums&lt;/code&gt;.
We know basically what it needs to do: find the first and last single-digit number on each line.
Some may be a literal digit, others may be an English word representing that digit.&lt;/p&gt;
&lt;p&gt;Here's the basic structure.
We break the string into its individual characters, then we find the first and last numbers.
We convert the numbers to strings, concatenate them together, then cast them &lt;em&gt;back&lt;/em&gt; to a number.
Finally we can hurl the result to our caller.&lt;/p&gt;
&lt;pre class="language-hurl " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-hurl"&gt;&lt;span&gt;let extract_nums = func(line) {
&lt;/span&gt;&lt;span&gt;    let chars = str_chars(line);
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    let first = 0;
&lt;/span&gt;&lt;span&gt;    # TODO: find the first number
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    let last = 0;
&lt;/span&gt;&lt;span&gt;    # TODO: find the last number
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    let first = &amp;quot;&amp;quot; + first;
&lt;/span&gt;&lt;span&gt;    let last = &amp;quot;&amp;quot; + last;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    hurl as_num(first + last);
&lt;/span&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finding the first and last number are essentially the same, just one starts from the end, so I'll skip one here.
To find the first number, we use &lt;a href="https://git.sr.ht/~ntietz/hurl-lang/tree/main/item/lib/loop.hurl"&gt;&lt;code&gt;until&lt;/code&gt;&lt;/a&gt; (defined in Hurl in the standard library) to iterate through the list until we find a number, then we break.
The index is tracked in an element of a list, passed in as &lt;code&gt;[0]&lt;/code&gt; here to start iteration at the beginning.&lt;/p&gt;
&lt;p&gt;For each element of the list, our condition for stopping is "is this a number?" and if so, we save it and halt iteration.
Otherwise we run the loop body, which increments our index for the next iteration.&lt;/p&gt;
&lt;pre class="language-hurl " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-hurl"&gt;&lt;span&gt;try {
&lt;/span&gt;&lt;span&gt;    until(func(locals) {
&lt;/span&gt;&lt;span&gt;        try {
&lt;/span&gt;&lt;span&gt;            is_number(line, locals.1);
&lt;/span&gt;&lt;span&gt;            # TODO: define is_number
&lt;/span&gt;&lt;span&gt;        } catch as result {
&lt;/span&gt;&lt;span&gt;            if(func() {
&lt;/span&gt;&lt;span&gt;                hurl result.1;
&lt;/span&gt;&lt;span&gt;            }, func() {
&lt;/span&gt;&lt;span&gt;                first = result.2;
&lt;/span&gt;&lt;span&gt;            });
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;            hurl result.1;
&lt;/span&gt;&lt;span&gt;        };
&lt;/span&gt;&lt;span&gt;    }, func(locals) {
&lt;/span&gt;&lt;span&gt;        hurl [locals.1 + 1];
&lt;/span&gt;&lt;span&gt;    }, [0]);
&lt;/span&gt;&lt;span&gt;} catch as val {
&lt;/span&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that just leaves the last bit, which is defining our &lt;code&gt;is_number&lt;/code&gt; function.
This one leverages the &lt;a href="https://git.sr.ht/~ntietz/hurl-lang/tree/main/item/lib/if.hurl"&gt;if_else&lt;/a&gt; also defined in the standard library, and also takes advantage of Hurl's lists being 1-indexed.
We keep track of the result as a list of &lt;code&gt;[boolean, int]&lt;/code&gt; to indicate whether it is a number and, if so, what the number is.&lt;/p&gt;
&lt;p&gt;The first thing we do is check the condition for whether the current index points to a digit.
If so, we go to the true case, and we set the result to the character at that position cast to a number.
Otherwise, we loop through a list of the first 9 numbers written as English words, and check whether or not they're contained as a substring starting at our index.
If so, we set the result.&lt;/p&gt;
&lt;p&gt;And at the end, we just hurl what we found!&lt;/p&gt;
&lt;pre style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code&gt;&lt;span&gt;let is_number = func(line, index) {
&lt;/span&gt;&lt;span&gt;    let result = [false, 0];
&lt;/span&gt;&lt;span&gt;    if_else(func() {
&lt;/span&gt;&lt;span&gt;        hurl is_digit(at(line, index));
&lt;/span&gt;&lt;span&gt;    }, func() {
&lt;/span&gt;&lt;span&gt;        result = [true, as_num(at(line, index))];
&lt;/span&gt;&lt;span&gt;    }, func() {
&lt;/span&gt;&lt;span&gt;        try {
&lt;/span&gt;&lt;span&gt;            for(9, func(locals) {
&lt;/span&gt;&lt;span&gt;                let target = at(word_numbers, locals.1);
&lt;/span&gt;&lt;span&gt;                let slice = slice(line, index, index + len(target));
&lt;/span&gt;&lt;span&gt;                if(func() {
&lt;/span&gt;&lt;span&gt;                    hurl slice == target;
&lt;/span&gt;&lt;span&gt;                }, func() {
&lt;/span&gt;&lt;span&gt;                    result = [true, locals.1];
&lt;/span&gt;&lt;span&gt;                });
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;                hurl [];
&lt;/span&gt;&lt;span&gt;            }, []);
&lt;/span&gt;&lt;span&gt;        } catch as default {
&lt;/span&gt;&lt;span&gt;        };
&lt;/span&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    hurl result;
&lt;/span&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then it can all be put together into one listing, and we run it and it works!
It gets the right answer and takes a good long time to compute it, but it does get there eventually.&lt;/p&gt;
&lt;p&gt;You can see the &lt;a href="https://git.sr.ht/~ntietz/hurl-lang/tree/main/item/aoc/day1.2.hurl"&gt;full listing&lt;/a&gt; in the repo.
I've changed the order of things here and skipped a few pieces here to present it more easily, but otherwise it's the same code.&lt;/p&gt;
&lt;h1 id="plans-for-next-advent-of-code"&gt;Plans for next Advent of Code&lt;/h1&gt;
&lt;p&gt;So, this was fun.
Honestly, it was enjoyable to do a few days of Advent of Code in this, uh, treat of a language.
(It was my penance.)
But I only really wanted to do it for a few days.
Next year, what will I do?&lt;/p&gt;
&lt;p&gt;There are a few options I'm considering for next year:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Assembly:&lt;/em&gt; this seems like a fun challenge for a few days, although probably fairly mind bending! That could be a benefit.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Languages made by other Recurse Center people:&lt;/em&gt; this could be a fun way to storm through multiple languages, like one of my RC friends did one year.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Next year's language:&lt;/em&gt; I intend to make another language next year (something more normal, to focus on some of the "make it nice to use" aspects). If I follow through, I'll have to use it at least some!&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Something... normal?&lt;/em&gt; I could always use Python or Rust and do some of it the normal way! It's fun to go through it with friends, so I could go back to doing it socially next year!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Advent of Code is a lot of fun, and it's a nice way to get exposure to different ideas and new languages and new technologies, so I'm excited to see what penance I bring on myself for the next one!&lt;/p&gt;
&lt;hr /&gt;
&lt;hr /&gt;&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-maybe"&gt;
&lt;p&gt;Well, I do intend to submit something about Hurl to &lt;a href="https://sigbovik.org/"&gt;SIGBOVIK&lt;/a&gt; so if that gets accepted, people will hear about it again. &lt;a href="https://ntietz.com/blog/three-days-of-advent-of-code-in-hurl/#fr-maybe-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-1"&gt;
&lt;p&gt;We are far enough into Advent of Code that posting the solutions is fine, I think. Also props to anyone who actually gets this running or translates the Hurl code into something else, so go for it. &lt;a href="https://ntietz.com/blog/three-days-of-advent-of-code-in-hurl/#fr-1-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 18 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/three-days-of-advent-of-code-in-hurl/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Paying Netflix $0.53/h, etc.</title><link>https://taylor.town/dollar-per-hour</link><description>People pay ¨D0.50-¨D2.00 for an hour of digital entertainment.</description><author>taylor.town</author><pubDate>Mon, 18 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/dollar-per-hour</guid></item><item><title>Colliding Secure Hashes</title><link>https://www.da.vidbuchanan.co.uk/blog/colliding-secure-hashes.html</link><author>David Buchanan's Blog</author><pubDate>Mon, 18 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.da.vidbuchanan.co.uk/blog/colliding-secure-hashes.html</guid></item><item><title>Mediasonic Probox fan replacement</title><link>https://voussoir.net/writing/mediasonic_fan_replacement</link><description>&lt;article&gt;
&lt;p&gt;&lt;a href="/writing"&gt;Back to writing&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a class="tag_link" href="/writing/tags/electronics"&gt;[electronics]&lt;/a&gt; &lt;a class="tag_link" href="/writing/tags/today_i_did_this"&gt;[today_i_did_this]&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Mediasonic Probox fan replacement&lt;/h1&gt;
&lt;p&gt;I have a Mediasonic Probox for file storage. After 3.5 years of use (purchased May 2020), one of the fans has gone bad:&lt;/p&gt;
&lt;audio controls="" src="/writing/mediasonic_fan_replacement/before.flac"&gt;&lt;/audio&gt;&lt;p&gt;The stock fans are labeled YLTC DFS802012H, 12 Volts, 0.23 Amps, and measure 80x80x20mm.&lt;/p&gt;
&lt;p&gt;For my replacement, I chose the Arctic P8 because it seems to have a good reputation for being quiet, and I feel that a pressure-optimized fan (the P series) is more appropriate than a flow-optimized fan (the F series) for pushing air around the box's motherboard and between the drives.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/mediasonic_fan_replacement/old_fan_new_fan.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Before doing some research, it hadn't occurred to me that the thicknesses of 80mm case fans wouldn't all be the same. On this &lt;a href="/writing/mediasonic_fan_replacement/mediasonic_forum_4054.html"&gt;Mediasonic forum post&lt;/a&gt; about fan replacement, someone commented that most fans on the market are 25mm thick and would not be a drop-in replacement for the 20mm stock. The screws are not long enough to go all the way through a 25mm fan, and the shroud, and have any real bite left over.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/mediasonic_fan_replacement/fan_thickness.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Besides the problem of length, there is also diameter: without the shroud, the screws slip straight through the mounting holes in the fan itself, so the shroud is necessary... or so I thought. Then I found &lt;a href="https://www.thingiverse.com/thing:4965892" title="Mediasonic Probox fan stanchion by fl0rm"&gt;this thingiverse thing&lt;/a&gt; where the author takes advantage of the empty space between the two faces of the fan housing to slip in a little hollow post. This bypasses the extra 5mm of fan thickness by placing the screw head within the Z-profile of the fan, and gives it something to grab onto, solving both problems at once! Very clever!&lt;/p&gt;
&lt;div class="highlight openscad"&gt;&lt;pre&gt;&lt;span class="o"&gt;$fa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;$fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nb"&gt;difference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;cylinder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;5.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nb"&gt;cylinder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Please excuse the poor quality of the prints...&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/mediasonic_fan_replacement/standoffs.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/mediasonic_fan_replacement/standoff_length.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Also, most fans use a three-pin connector with one wire carrying RPM signal data, but the Mediasonic only uses the two power pins and a 2mm connector. There are &lt;a href="https://www.aliexpress.us/item/2251832624753638.html" title="ARSYLID Conversion cable 3 pin to 2 pin 2.0mm adapter fan cable 12V cooler fan for VGA cooling fan 2pin micro-2pin"&gt;adapters&lt;/a&gt; out there, mentioned in the forum post, so you can make the swap non-destructively. I was not in the mood to wait multiple weeks for aliexpress shipping, so I cut and soldered the ends.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/mediasonic_fan_replacement/wires_cut.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/mediasonic_fan_replacement/wires_soldered.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Of course, you could just buy a fan that's 20mm thick with a two-pin connector in the first place. But all the options I saw were very cheap/generic looking, and the per-fan prices were not cheaper than the Arctics, and I've been wanting to replace the stock fans with quieter ones for a long time anyway. In this case, I'd rather buy a fan from a known brand with a good reputation for quiet, even if it requires some modification.&lt;/p&gt;
&lt;p&gt;You have to admit this doesn't really seem like the ideal air path:&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/mediasonic_fan_replacement/guts.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;But the replacement went just fine and the fan feels very secure.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/mediasonic_fan_replacement/install_1.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/mediasonic_fan_replacement/install_2.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/mediasonic_fan_replacement/install_3.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/mediasonic_fan_replacement/install_4.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;I felt I should add a bit of tape around the edge to discourage air from leaking into the sides, since the shroud is gone now.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/mediasonic_fan_replacement/tape_seal.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;After this, I also replaced the other fan in the same manner.&lt;/p&gt;
&lt;p&gt;At low speed the new fans are essentially inaudible:&lt;/p&gt;
&lt;audio controls="" src="/writing/mediasonic_fan_replacement/after_low.flac"&gt;&lt;/audio&gt;&lt;p&gt;At full speed, they're still pretty quiet:&lt;/p&gt;
&lt;audio controls="" src="/writing/mediasonic_fan_replacement/after_high.flac"&gt;&lt;/audio&gt;&lt;p&gt;These recordings were made with no hard drives in the box. Once the drives are in... well... it's quieter than it was before but it's not exactly life-changing.&lt;/p&gt;
&lt;p&gt;For the first few days, I heard a new sound that made me think the whole thing was pointless — it was a kind of ringing, or resonance, or beat frequency that was audible when I sat in my chair, but seemed to disappear when I got close to the box to listen for it. I'm not sure if it was an illusion, or the &lt;a href="https://en.wikipedia.org/wiki/The_Tell-Tale_Heart"&gt;heart&lt;/a&gt; of my latest victim, or the bearings just breaking themselves in, but it's better now.&lt;/p&gt;
&lt;p&gt;I don't normally publish this kind of thing, since I feel it's a little trivial or unimpressive, and because it mentions &lt;a href="/writing/advertixing#HailCorporate"&gt;brand names&lt;/a&gt;. But whenever I am solving a problem, I rely on posts made by other people solving the same problem — something as simple as a hollow cylinder turned out to be a very helpful bit of inspiration to me. If that person thought a cylinder was too trivial to share, it would have taken me longer to do this. So maybe I should contribute back in the same way more often.&lt;/p&gt;

&lt;hr /&gt;
&lt;p&gt;&lt;a href="https://git.voussoir.net/voussoir/voussoir.net/commits/master/voussoir.net/writing/mediasonic_fan_replacement/mediasonic_fan_replacement.md"&gt;View this document's history&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://git.voussoir.net/voussoir/voussoir.net/commit/2ec2455a986c5d725288e69eab90babc9011e3c4"&gt;&lt;time datetime="2023-12-17T12:50:46-08:00"&gt;2023-12-17&lt;/time&gt; Add mediasonic_fan_replacement.md.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;address&gt;Contact me: writing@voussoir.net&lt;/address&gt;
&lt;p&gt;If you would like to subscribe for more, add this to your RSS reader: &lt;a href="/writing/writing.atom" rel="alternate" type="application/atom+xml"&gt;https://voussoir.net/writing/writing.atom&lt;/a&gt;&lt;/p&gt;
&lt;/article&gt;</description><author>voussoir.net/writing</author><pubDate>Sun, 17 Dec 2023 22:50:46 GMT</pubDate><guid isPermaLink="true">https://voussoir.net/writing/mediasonic_fan_replacement</guid></item><item><title>Tracking Java Native Memory With JDK Flight Recorder</title><link>https://www.morling.dev/blog/tracking-java-native-memory-with-jdk-flight-recorder/</link><description>&lt;div class="toc" id="toc"&gt;
&lt;div id="toctitle"&gt;Table of Contents&lt;/div&gt;
&lt;ul class="sectlevel1"&gt;
&lt;li&gt;&lt;a href="#_an_example"&gt;An Example&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_tracking_rss"&gt;Tracking RSS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As regular readers of this blog will now, &lt;a href="https://openjdk.org/jeps/328"&gt;JDK Flight Recorder&lt;/a&gt; (JFR) is one of my favorite tools of the Java platform.
This low-overhead event recording engine built into the JVM is invaluable for observing the runtime characteristics of Java applications and identifying any potential performance issues.
JFR continues to become better and better with every new release,
with one recent addition being support for native memory tracking (NMT).&lt;/p&gt;
&lt;/div&gt;</description><author>Gunnar Morling</author><pubDate>Sun, 17 Dec 2023 20:29:00 GMT</pubDate><guid isPermaLink="true">https://www.morling.dev/blog/tracking-java-native-memory-with-jdk-flight-recorder/</guid></item><item><title>Making Cartridges for the Commodore C16, C116, and Plus/4</title><link>https://www.hackup.net/2023/12/making-cartridges-for-the-commodore-c16-c116-and-plus-4/</link><description>&lt;p&gt;As I mentioned a while ago, I had the opportunity to repair a bunch of broken boards for Commodore C16 and Plus/4 computers. This led to a couple of new projects, the ROMulator and the PLA264. To help with the &amp;#8230; &lt;a href="https://www.hackup.net/2023/12/making-cartridges-for-the-commodore-c16-c116-and-plus-4/"&gt;Continue reading &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
The post &lt;a href="https://www.hackup.net/2023/12/making-cartridges-for-the-commodore-c16-c116-and-plus-4/"&gt;Making Cartridges for the Commodore C16, C116, and Plus/4&lt;/a&gt; first appeared on &lt;a href="https://www.hackup.net"&gt;hackup.net&lt;/a&gt;.</description><author>hackup.net</author><pubDate>Sun, 17 Dec 2023 16:58:38 GMT</pubDate><guid isPermaLink="true">https://www.hackup.net/2023/12/making-cartridges-for-the-commodore-c16-c116-and-plus-4/</guid></item><item><title>Hosting quickly 2.5 - Discovering and calling a backend from Dioxus</title><link>https://blog.martijnarts.com/hosting-quickly-2-5-discovering-and-calling-a-backend-from-dioxus/</link><description>&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.martijnarts.com/hosting-quickly-1-setting-up-terraform-github-and-1password/"&gt;Hosting quickly 1 - Setting up Terraform, Github, and 1Password&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.martijnarts.com/hosting-quickly-2-dioxus-to-the-web-on-fly-io/"&gt;Hosting quickly 2 - Dioxus to the web on Fly.io&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We should make the backend serve some API that we can hit from the frontend.&lt;/p&gt;
&lt;p&gt;We&amp;apos;ll turn the root endpoint in the backend into a JSON-returning endpoint:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-rust"&gt;#[derive(&lt;/code&gt;&lt;/pre&gt;</description><author>Martijn Arts</author><pubDate>Sun, 17 Dec 2023 09:54:52 GMT</pubDate><guid isPermaLink="true">https://blog.martijnarts.com/hosting-quickly-2-5-discovering-and-calling-a-backend-from-dioxus/</guid></item><item><title>Book Review - The Field Guide to Global Payments</title><link>https://river.me/blog/book-review-global-payments/</link><description>A review of &amp;ldquo;A Field Guide to Global Payments,&amp;rdquo; including a table of initialisms used in the book.</description><author>River Writes - A MediaWiki Blog</author><pubDate>Sun, 17 Dec 2023 09:15:58 GMT</pubDate><guid isPermaLink="true">https://river.me/blog/book-review-global-payments/</guid></item><item><title>OnlineOrNot Diaries 17</title><link>https://maxrozen.com/onlineornot-diaries-17</link><description>On learning screencasting, cleaning up tech debt, and focus</description><author>Max Rozen</author><pubDate>Sun, 17 Dec 2023 09:10:00 GMT</pubDate><guid isPermaLink="true">https://maxrozen.com/onlineornot-diaries-17</guid></item><item><title>One possible death for Discord</title><link>https://utf9k.net/blog/one-possible-death-for-discord/</link><description>An unreleased post where I talk about Discord and what might happen to them if they were acquired</description><author>utf9k</author><pubDate>Sun, 17 Dec 2023 03:14:00 GMT</pubDate><guid isPermaLink="true">https://utf9k.net/blog/one-possible-death-for-discord/</guid></item><item><title>An basic Mullvad WireGuard setup for macOS</title><link>https://xenodium.com/a-quick-mullvad-macos-setup</link><description>&lt;p&gt;Needed a VPN to test an API from a different location. Gave &lt;a href="https://mullvad.net/en"&gt;Mullvad&lt;/a&gt; a try.&lt;/p&gt;
&lt;p&gt;Pretty neat, you can generate an account number without providing an email address. You can also pre-pay with a ton of options, including cash, crypto, credit cards, PayPal, wire transfers…&lt;/p&gt;
&lt;p&gt;After seeing your account credited, one can download a &lt;a href="https://mullvad.net/en/account/wireguard-config"&gt;generated WireGuard configuration&lt;/a&gt;. Also a WireGuard noob, so took this opportunity to give it a try.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://apps.apple.com/us/app/wireguard/id1451685025?mt=12"&gt;WireGuard macOS app&lt;/a&gt; has an &amp;quot;Import Tunnel(s) from File…&amp;quot; option where you can import the .conf file downloaded from &lt;a href="https://mullvad.net/en/account/wireguard-config"&gt;Mullvad's generated config&lt;/a&gt;. After that, all I had to do was click the &amp;quot;Activate&amp;quot; button and &lt;a href="https://en.wikipedia.org/wiki/Bob%27s_your_uncle"&gt;Bob's your uncle&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/a-quick-mullvad-macos-setup/wg-redact.png" /&gt;&lt;/p&gt;
&lt;p&gt;You can test your connection via:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;curl https://am.i.mullvad.net/connected
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I had a brief stint at using the command-line alternative via homebrew &lt;code&gt;brew install wireguard-go wireguard-tools&lt;/code&gt;, but that seems to fail silently:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.bash"&gt;wg-quick up xxxxx
[#] wireguard-go utun
[+] Interface for xxxxx is utun7
[#] wg setconf utun7 /dev/fd/63
[#] ifconfig utun7 inet xxx.xxx.xxx.xxx/xx xxx.xxx.xxx.xxx alias
[#] ifconfig utun7 inet6 xxxx:xxxx:xxxx:xxxx::x:xxxx/xxx alias
[#] ifconfig utun7 up
[#] route -q -n add -inet6 ::/1 -interface utun7
[#] route -q -n add -inet6 8000::/1 -interface utun7
[#] route -q -n add -inet xxx.xxx.xxx.xxx/x -interface utun7
[#] route -q -n add -inet xxx.xxx.xxx.xxx/x -interface utun7
[#] route -q -n add -inet xxx.xxx.xxx.xxx -gateway xxx.xxx.xxx.xxx
[#] networksetup -getdnsservers Wi-Fi
[#] networksetup -getsearchdomains Wi-Fi
[#] networksetup -getdnsservers iPhone USB
[#] networksetup -getsearchdomains iPhone USB
[#] networksetup -getdnsservers Thunderbolt Bridge
[#] networksetup -getsearchdomains Thunderbolt Bridge
[#] networksetup -getdnsservers xxxxx
[#] networksetup -getsearchdomains xxxxx
[#] networksetup -setdnsservers iPhone USB xxx.xxx.xxx.xxx
[#] networksetup -setsearchdomains iPhone USB Empty
[#] networksetup -setdnsservers xxxxx xxx.xxx.xxx.xxx
[#] networksetup -setsearchdomains xxxxx Empty
[#] networksetup -setdnsservers Wi-Fi xxx.xxx.xxx.xxx
[#] networksetup -setsearchdomains Wi-Fi Empty
[#] networksetup -setdnsservers Thunderbolt Bridge xxx.xxx.xxx.xxx
[#] networksetup -setsearchdomains Thunderbolt Bridge Empty
[+] Backgrounding route monitor
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;curl https://am.i.mullvad.net/connected
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I'm on a Macbook M1 Pro, running macOS Sonoma. If you got &lt;code&gt;wg-quick&lt;/code&gt; working on Sonoma, I'd love to hear from ya (&lt;a href="https://indieweb.social/@xenodium"&gt;Mastodon&lt;/a&gt; / &lt;a href="https://twitter.com/xenodium"&gt;Twitter&lt;/a&gt; / &lt;a href="https://www.reddit.com/user/xenodium"&gt;Reddit&lt;/a&gt; / &lt;a href="mailto:me__AT__xenodium.com"&gt;Email&lt;/a&gt;).&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Sun, 17 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/a-quick-mullvad-macos-setup</guid></item><item><title>F22a Raptor Part Names</title><link>https://studiofreya.org/posts/f22a-raptor-part-names/</link><description>&lt;p&gt;I saw this schematic of the F-22A raptor the other day. Enjoy!&lt;/p&gt;




















&lt;img alt="" src="https://studiofreya.org/posts/f22a-raptor-part-names/f22a-raptor.jpg" /&gt;</description><author>Studiofreya SSG Site</author><pubDate>Sun, 17 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://studiofreya.org/posts/f22a-raptor-part-names/</guid></item><item><title>Languishing</title><link>https://studiofreya.org/words/languishing/</link><description>&lt;ul&gt;
&lt;li&gt;Becoming languid and weak; pining; losing health and strength. Similar: pining Amorously pensive; indicating melancholy.&lt;/li&gt;
&lt;li&gt;&amp;ldquo;languishing eyes, or look&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Suffering neglect; neglected. Similar: neglected&lt;/li&gt;
&lt;/ul&gt;</description><author>Studiofreya SSG Site</author><pubDate>Sun, 17 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://studiofreya.org/words/languishing/</guid></item><item><title>The most frustrating thing in your life</title><link>https://taylor.town/frustrating-thing</link><description>...is a computer.</description><author>taylor.town</author><pubDate>Sun, 17 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/frustrating-thing</guid></item><item><title>Cynical PM Framework Webinar at Product School</title><link>https://www.ft.io/blog/product-school-webinar/</link><description/><author>Frank Tisellano | ft.io</author><pubDate>Sun, 17 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.ft.io/blog/product-school-webinar/</guid></item><item><title>Summary: 10-Minute Toughness by Jason Selk</title><link>https://www.chestergrant.com/summary-10-minute-toughness-by-jason-selk</link><description>&lt;div class="posthaven-post-body"&gt;&lt;center&gt;        &lt;div class="posthaven-gallery" id="posthaven_gallery[2088002]"&gt;
                  &lt;p class="posthaven-file posthaven-file-image posthaven-file-state-processed"&gt;
          &lt;img class="posthaven-gallery-image" src="https://phaven-prod.s3.amazonaws.com/files/image_part/asset/3119275/OqNlystgs4iddjuDfKBH-5ehnDg/medium_cover.png" /&gt;
        &lt;/p&gt;

        &lt;/div&gt;
&lt;/center&gt;&lt;p&gt;1. The 10-Minute Toughness program is simply about identifying and utilizing a handful of mental tools that are proven to help people perform more consistently.&lt;/p&gt;&lt;p&gt;2. Athletes using 10-MT spend approximately four to five minutes after each practice and competition to focus on strengths and goal-related thinking. They also spend five minutes a day completing a five-step mental workout prior to the next day's training and/or competition to help develop a pinpoint focus on what they are trying to improve and what it takes to make it happen.&lt;/p&gt;&lt;p&gt;&lt;b&gt;3. The field of sport psychology has identified self-efficacy (self-confidence) as the most influential mental variable in controlling performance. This means that if you have a strong belief in your ability to perform well, then the chances of your actually performing well greatly improve. This program is specifically designed to help athletes improve self-confidence. The two most effective ways to develop self-confidence are to perform well and to physically and mentally prepare to perform well.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;4. The program works because it has two primary strengths. First, it forces athletes to identify the "process of success." The process of success is a clear and concise assessment of what it takes for the athlete to perform well. This assessment is done daily after training and competition. Second, a mental workout provides a vehicle for mentally training the identified process of success. In addition, athletes visualize success outcomes to enhance self-confidence. They identify the most beneficial thoughts to have and then condition their minds to be able to maintain those thoughts more fully throughout training sessions and competition. It is certainly not rocket science, but it absolutely works!&lt;/p&gt;&lt;p&gt;5. There are three essential phases in the 10-MT mental-training program. Phase 1 is what I refer to as the mental workout. This individualized mental-training plan helps athletes identify and focus on their "control points," or what it takes for success.&lt;/p&gt;&lt;p&gt;6. 10-Minute Toughness Mental Workout:&lt;/p&gt;&lt;blockquote&gt;
&lt;p&gt;a. &lt;b&gt;The Centering Breath&lt;/b&gt;. A fifteen-second deep breath designed to control arousal states. &lt;/p&gt;
&lt;p&gt;b. &lt;b&gt;The Performance Statement&lt;/b&gt;. A specifically tailored self-statement useful for increasing training and competitive focus. &lt;/p&gt;
&lt;p&gt;c. &lt;b&gt;The Personal Highlight Reel&lt;/b&gt;. An advanced form of visualization allowing athletes to increase skill refinement and consistency. &lt;/p&gt;
&lt;p&gt;d. &lt;b&gt;The Identity Statement&lt;/b&gt;. A concrete self-statement proven to enhance self-image and performance confidence. &lt;/p&gt;
&lt;p&gt;e. &lt;b&gt;The Centering Breath&lt;/b&gt;. As in step one, a biologically established relaxation technique used to increase the potential to perform well under pressure.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;7. Phase 2 comprises developing and utilizing an effective goal-setting program:&lt;/p&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;b&gt;Vision clarity:&lt;/b&gt; Ultimate goal accomplishment associated with sport. &lt;/p&gt;
&lt;p&gt;&lt;b&gt;Product goal:&lt;/b&gt; A result-oriented goal that is clearly measurable and is usually most effective if it emphasizes accomplishments in the next twelve months. &lt;/p&gt;
&lt;p&gt;&lt;b&gt;Process goal:&lt;/b&gt; The daily action needed to accomplish both product-and vision-level goals. &lt;/p&gt;
&lt;p&gt;&lt;b&gt;Success Log:&lt;/b&gt; Questions that encourage the identification of personal strengths and specific desires for improvement.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Personal Rewards Program Questionnaire:&lt;/b&gt; Questionnaire that identifies the athlete's motivational preferences. &lt;/p&gt;
&lt;p&gt;&lt;b&gt;Personal incentive style:&lt;/b&gt; The motivational preferences of athletes that enhance training and competition intensity.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;8. Phase 3 teaches individuals how to develop a relentless focus on solutions. &lt;/p&gt;&lt;p&gt;9. Athletes and coaches are able to realize the small difference between good and great by asking, What is one thing I can do that could make this better?&lt;/p&gt;&lt;p&gt;10. Centering Breath = Diaphragm Breathing - I have tried to simplify diaphragm breathing by qualifying a good centering breath as one that lasts fifteen seconds. The formula is 6-2-7: breathe in for six seconds, hold for two, and breathe out for seven seconds. Individuals under the age of twelve should try to have the centering breath last eleven seconds (4-2-5).&lt;/p&gt;&lt;p&gt;11. One core aspect of training is known as arousal control. The heart rate is a primary control of a person's arousal state. It is important to control heart rate because using the mind effectively becomes increasingly more difficult as the heart rate rises. Once the rate gets to 120 beats per minute, the mind will not be nearly as sharp (unless proper conditioning and mental training has occurred), and at about 150 beats per minute, the mind will essentially shut down and go into survival mode. (In this state, even the best athletes will lose the ability to maintain mental acuity.)&lt;/p&gt;&lt;p&gt;12. A performance statement is a specifically designed form of self-talk. Self-talk is the conversation that goes on in a person's head throughout the day. It is said that the average person has up to sixty thousand thoughts per day—that's a lot of self-talk. The unfortunate thing about those thoughts is that the majority tend to embody self-doubt or negativity. If we do not choose our thoughts carefully, they can (and many times do) have a negative impact on performance.&lt;/p&gt;&lt;p&gt;13. From a mental standpoint, the most tried-and-true way to increase performance is to improve confidence. Self-talk is one of the most influential agents for honing self-confidence.&lt;/p&gt;&lt;p&gt;&lt;b&gt;14. Extensive research in the sport psychology world confirms that an athlete's internal dialogue significantly influences performance. Athletes who have negative self-talk will generally experience poor performance; conversely, when athletes keep their minds focused on positive performance cues, they are more likely to experience success.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;15. A performance statement is a type of self-talk designed to help athletes zoom in on one specific thought to enhance performance consistency. It is a simple, yet concrete, thought that specifically identifies the process of success, or what it takes to perform at your best.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;16. For the baseball player, there may be a performance statement to emphasize hitting (track the ball, smooth and easy) and defense (set, stay down, watch it into the glove). A basketball player may choose to combine both offense and defense into one performance statement (hustle every possession; attack every rebound; drive, drive, drive), while the gymnast may have a performance statement for every event she competes on (floor: quick hands, tight legs, squeeze; vault: top speed, feet in front; bars: hollow handstands and elbows locked).&lt;/p&gt;&lt;p&gt;17. In my opinion, the essence of mental toughness is the ability to replace negative thinking with thoughts that are centered on performance cues or that contribute to improved self-confidence.&lt;/p&gt;&lt;p&gt;&lt;b&gt;18. The more often negative thoughts are replaced with positive self-talk, the more successful and mentally tough a person will be.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;19. The most helpful method to stop self-doubt and negative thinking is thought replacement. Effective thought replacement occurs when you decide what you want to have happen and then think more often about what it will take to make it happen. Whenever unproductive thoughts ("don't" thinking or mental clutter) infringe, replace them with productive ones.&lt;/p&gt;&lt;p&gt;20. The performance statement serves two principal purposes. First, it is a way to avoid self-doubt, negativity, or mental clutter(thought replacement). Second, it helps you perform at your best by directing your thoughts toward targeted areas of strength.&lt;/p&gt;&lt;p&gt;21. A client trying to prioritize exercise had this as his performance statement: "Three days on, one day off; dedicated and committed; I clear my own path." His highlights aren't as much about the technical aspects of doing it right as they are about his desire and ability to be healthy.&lt;/p&gt;&lt;p&gt;22. The personal highlight reel is an advanced form of visualization in which you create your own mental "SportsCenter" highlight reel.&lt;/p&gt;&lt;p&gt;23. People learn faster by visualizing success rather than by watching it on tape. Of course, there is considerable value in watching success on tape. In fact, during film sessions, Bear Bryant, the famous football coach at Alabama, showed his players only footage of themselves playing well.&lt;/p&gt;&lt;p&gt;24. Coach Bryant contended that showing his players what they did well helped them repeat the performance, whereas showing them what they did wrong would only increase the likelihood of their exhibiting more poor play in the future.&lt;/p&gt;&lt;p&gt;25. According to some studies, in fact, every minute of visualization is worth seven minutes of physical practice.&lt;/p&gt;&lt;p&gt;26. Visualizing is the act of watching something in your mind before actually doing it.&lt;/p&gt;&lt;p&gt;27. There are eight essential guidelines for visualization success:&lt;/p&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;b&gt;Choose One of Three Camera Angles&lt;/b&gt; - whether 1. as if the camera is in the stands recording someone other than you performing the skill; 2. you are watching the mental video as if the camera is in the stands filming as you perform the skill and 3. you watch the mental video as if your eyes are the camera lens (or as if you're wearing a helmet camera). From this perspective, you would see whatever you actually see while you are performing the skill.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Pay Attention to Detail&lt;/b&gt; - Try to pay attention to three of the five senses while performing the skill: sight (What do you see on the surrounding field, court, or arena?); sound (Do you hear crowd noise, coaches, teammates?); and feel (What does the ball, bat, racket, club, etc., feel like? Also, what does your body feel like as it performs successfully?).&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Frequent and Brief Is the Ticket&lt;/b&gt; - Visualizing many times for short stints is far more effective than visualizing for extended periods.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Visualize from Beginning to End&lt;/b&gt; - Make sure to view the skill or action in its entirety. Creating a comprehensive mental video helps to reduce distraction and eliminate potential problems with emotional control.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Emotionally Feel the Way You Want to Feel&lt;/b&gt; - Be aware of how you want to feel before and during competition, and then train yourself to feel just that way.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Replay Until You Get It Right&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Give Credit Where Credit Is Due&lt;/b&gt; - Upon finishing each successful visualization, take a brief time-out to congratulate yourself on a job well done. A mental pat on the back or a few kind words to yourself after visualizing success will help keep you on an even keel.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Operate at Game Speed&lt;/b&gt; - Finally, watch the mental clip at the desired speed. While it is helpful sometimes to slow the mental video down to figure out some of the more complex skills, you should always visualize at the desired speed prior to the physical performance, or else the timing of the action may be off.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;28. Camera angle number three is the most beneficial, because with this approach, your muscles can actually get stronger and muscle memory can develop. Many athletes refer to muscle memory as the ability to perform successfully while on autopilot.&lt;/p&gt;&lt;p&gt;29. Although I just told you that all three camera angles are helpful, I do not want you to use more than one camera angle when you visualize. Pick one angle to employ, trusting your instincts on which of the three is most appropriate for you.&lt;/p&gt;&lt;p&gt;30. The personal highlight reel is made up of three parts. The first part emphasizes a successful performance from your past, and the second and third parts address how you want to perform in the future.&lt;/p&gt;&lt;p&gt;31. Part 1: Sixty-Second Mental Video Clip of Excellent Past Performance - There are two options to consider when creating the first part of your personal highlight reel. The first option is to identify the best performance or game you have had in the past (the more recent the better). Imagine that the entire performance was videotaped; select three to five personal highlights from the videotaped performance, each lasting from ten to twenty seconds. The second option is to identify a few of your best performances and choose a combination of your best highlights from those performances. Choose the three to five best personal highlights, each lasting between ten to twenty seconds.&lt;/p&gt;&lt;p&gt;32. Parts 2 and 3: Sixty-Second Mental Video Clips of Upcoming Big Games or Competitions - The second and third parts are also sixty-second mental video clips, but instead of re-creating the past, you imagine what you want to have happen in your future.&lt;/p&gt;&lt;p&gt;33. Be sure to really hone in on what it physically feels like to emphasize your performance statement and emotionally focus on your ideal arousal state as you go through parts two and three of your personal highlight reel. By doing so, you will dramatically increase the effectiveness of your visualization.&lt;/p&gt;&lt;p&gt;34. Sean Townsend's Personal Highlight Reel &lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Part 1 (Successful Past Performance): World Championships '02  &lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;Good training leading up to World Championships  &lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;Felt calm and confident in warm-ups; everything was smooth and fluid  &lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;Hit four solid routines on the first day of competition&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;Superhit P-bars to win the gold  &lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;Personal greatest moment in sport: gold medal ceremony (freeze frame: standing at the top of the podium) &lt;/p&gt;&lt;/blockquote&gt;
&lt;/blockquote&gt;&lt;blockquote&gt;&lt;p&gt;Part 2 (Next Elevated-Pressure Competition): USA Championships '08  &lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;Good, solid warm-up, feeling calm and confident and taking one skill at a time, one routine at a time  &lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;Hit all six routines on day one while feeling calm and confident and taking one skill at a time, one routine at a time  &lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;Hit all six routines in finals while feeling calm and confident and taking one skill at a time, one routine at a time&lt;/p&gt;&lt;/blockquote&gt;
&lt;/blockquote&gt;&lt;blockquote&gt;&lt;p&gt;Part 3 (Next Competition): WOGA Invitational  &lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;Good, solid warm-up, feeling calm and confident and taking one skill at a time, one routine at a time  &lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;Hit all six routines on day one while feeling calm and confident and taking one skill at a time, one routine at a time &lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt; Hit all six routines in finals while feeling calm and confident and taking one skill at a time, one routine at a time&lt;/p&gt;&lt;/blockquote&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;35. An identity statement is a self-statement designed to improve self-image. Your self-image is essentially how you view yourself—what strengths and weaknesses you believe you possess. It has been demonstrated that what people believe they are capable of accomplishing largely determines how much they will actually accomplish.&lt;/p&gt;&lt;p&gt;36. Self-image is a proven agent of behavior control.&lt;/p&gt;&lt;p&gt;&lt;b&gt;37. The effect of self-image is one reason why 80 percent of lottery winners file for bankruptcy within five years of winning. Even though their financial situation has dramatically changed, typically the self-image hasn't. For people who see themselves as not good with money, no matter how much money is given to them, they will generally find a way to lose it.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;38. As pointed out previously, largely what determines people's self-image is the things they continually say to themselves, and unfortunately, much of our inner dialogue regards what we can't do rather than what we can do.&lt;/p&gt;&lt;p&gt;39. The key is to create the self-image desired—decide who you want to be and how you want to live—and then continuously tell yourself that you have what it takes to be that person. The self-image will guide and direct actions and behaviors until the self-image becomes the reality.&lt;/p&gt;&lt;p&gt;&lt;b&gt;40. In the words of Maxwell Maltz, "You will act like the sort of person you conceive yourself to be. More important, you literally cannot act otherwise, in spite of all your conscious efforts or willpower. This is why trying to achieve something difficult with teeth gritted is a losing battle. Willpower is not the answer. Self-image management is."&lt;/b&gt;&lt;/p&gt;&lt;p&gt;41. I encourage athletes to create a two-part identity statement. The first part indicates a strength you currently have or want to have. Be sure the strength you choose to accentuate makes achieving success more likely. For example, your strength might be that you are a really hard worker. Whether it is already true or is something that you want to be true, the first part of your identity statement might be "I am the hardest worker on the team."&lt;/p&gt;&lt;p&gt;42. The second part of your identity statement addresses what you want to accomplish. It is OK to stretch a bit here. According to research on affirmations, the more imposing the desired task, the more impact it will have on the self-image. Frame both parts of your identity statement as though the objectives have already been achieved.&lt;/p&gt;&lt;p&gt;43. My own personal identity statement is "I am more motivated than my competition; I am the most effective sport psychology consultant in the world."&lt;/p&gt;&lt;p&gt;44. Examples of Identity Statements  &lt;/p&gt;&lt;blockquote&gt;
&lt;p&gt;Baseball. I am the hardest worker on the team; I am a dominant major-league hitter.  &lt;/p&gt;
&lt;p&gt;Basketball. I am intelligent and I know this game better than anyone; I am the most prolific scorer on the court.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;45. Remember: the centering breath is a deep breath used to physiologically control heart rate and arousal. Taking a centering breath at the end of the mental workout is necessary for athletes because completing the personal highlight reel may cause the heart rate and arousal state to elevate.&lt;/p&gt;&lt;p&gt;46. I tell athletes that doing the mental workout one time a day is great. Some clients prefer to do it a couple of times a day, and that is OK, but there is no need to do it more than twice a day.&lt;/p&gt;&lt;p&gt;47. Give it a try. Complete the mental workout for two weeks, and judge for yourself if it helps you to improve focus, ability, and consistency.&lt;/p&gt;&lt;p&gt;48. The three levels, or types, of goals that I discuss with clients are ultimate goals, product goals, and process goals&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;49. Ultimate goals. Ultimate goals are the culmination of what you want to accomplish and how you want to accomplish it. When identifying your ultimate goals, imagine being able to look into the future and witness your retirement dinner. What accomplishments do you want to hang on the wall, and what would you like the speaker to say about you regarding how you played the game and how you conducted yourself?&lt;/p&gt;&lt;p&gt;50. Product goals. Product goals are result-oriented goals. They are clearly measurable and usually are most effective if they emphasize accomplishments in the next twelve months. I have found that the best formula is to assign yourself up to three product goals for the next competitive season in which you will participate and, again, up to three product goals for the upcoming off-season. For example, a basketball player may have the following three product goals for the season:&lt;/p&gt;&lt;blockquote&gt;
&lt;p&gt;1. Score at least ten points per game &lt;/p&gt;
&lt;p&gt;2. Have a free-throw percentage of 80 percent or better &lt;/p&gt;
&lt;p&gt;3. Grab at least six rebounds every game&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;51. Process goals. Process goals are the "what it takes" to achieve the product goals you set. Process goals also must be specific enough to be measurable. For example, the same basketball player may believe that two of the best ways for her to score ten points per game are by being mentally prepared for each game and by aggressively driving to the hoop (within five feet) at least four times per game.&lt;/p&gt;&lt;p&gt;52.It is important to write your goals down and let others know of your intentions. Writing and talking about your goals will also increase your accountability and motivation for achieving them.&lt;/p&gt;&lt;p&gt;53. "What does Coach Wooden say about excuses?" He replied, "Never make excuses. Your friends won't need them, and your foes won't believe them."&lt;/p&gt;&lt;p&gt;54. For goals to work, they must become a part of daily training.&lt;/p&gt;&lt;p&gt;55. After practices and games, you will take about three to four minutes to fill out a Success Log. The Success Logs ask athletes to answer the following questions:  &lt;/p&gt;&lt;blockquote&gt;
&lt;p&gt;What three things did I do well today?  &lt;/p&gt;
&lt;p&gt;Based on today's performance, what do I want to improve?  &lt;/p&gt;
&lt;p&gt;What is one thing I can do differently that could lead to the desired improvement?&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;56. Just before doing your mental workout, you will take one minute to review your Success Log entries from the previous day.&lt;/p&gt;&lt;p&gt;57. In my experience, athletes who establish a "personal rewards" system find it easier and more enjoyable to make the sacrifices necessary to achieve their goals.&lt;/p&gt;&lt;p&gt;58. Research strongly suggests that pushing to reach a goal provides more happiness than actually reaching the goal.&lt;/p&gt;&lt;p&gt;59. It is also important to set new goals once a goal is achieved. Remember that you stand to experience more joy and satisfaction from striving to reach your goals than from actually achieving them. So, keep replacing achieved goals with bright, shiny new ones.&lt;/p&gt;&lt;p&gt;&lt;b&gt;60. Edwin Locke and Gary Latham, discovered after years of research on thousands of people is that the most successful men and women in the world use goals as their primary method of motivation.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;61. Being solution focused means keeping your thoughts centered on what you want from life and what it takes to achieve what you want, as opposed to allowing thoughts of self-doubt and concern to occupy the mind.&lt;/p&gt;&lt;p&gt;62. "Every time I catch myself feeling angry or scared or depressed it is because I am thinking about what I don't have in my life or about something that is wrong with me. The instant I catch myself feeling uncomfortable I ask, 'What is one thing I can do right now that could make my situation better?,'&lt;/p&gt;&lt;p&gt;63. Bolster yourself. Surround yourself with people who want to scale the same heights. If your training companions are always complaining and wallowing in their problems, your solution focus is sure to waver.&lt;/p&gt;&lt;p&gt;64. Attitudes are contagious; people tend to take on the attitudes and actions of the individuals in their circle. A good attitude is just as contractible as a bad one. If you habitually conduct yourself with a solution focus, those around you will start to follow your model.&lt;/p&gt;&lt;p&gt;65. I consider an individual to be mentally tough when the mind is in control of thoughts that help the body accomplish what is wanted.&lt;/p&gt;&lt;p&gt;66. Let's review how the three phases of 10-MT can help you choose the right mental attitude.&lt;/p&gt;&lt;p&gt;Phase 1: The Mental Workout&lt;/p&gt;&lt;p&gt;Step 1: Centering Breath The centering breath is a fifteen-second breath in which you breathe in for six seconds, hold for two, and then breathe out for seven.&lt;/p&gt;&lt;p&gt;Step 2: Performance Statement After taking your centering breath, repeat to yourself the statement that most effectively focuses you on what it takes for you to be successful in competition.&lt;/p&gt;&lt;p&gt;Step 3: Personal Highlight Reel After reciting your performance statement, spend about three minutes visualizing what it looks like to be successful.&lt;/p&gt;&lt;p&gt;Step 4: Identity Statement Upon completing your personal highlight reel, repeat to yourself your identity statement to help mold your self-image.&lt;/p&gt;&lt;p&gt;Step 5: Centering Breath The mental workout ends the way it begins, with a fifteen-second deep breath. This breath resets your heart rate to a level of controlled arousal and increased mental focus. Use success logs to keep your daily training focused and precise. As best as you can, try to identify just one thing you want to improve prior to each training day.&lt;/p&gt;&lt;p&gt;Phase 2: Goal Setting for Greatness - When you know what you want to accomplish, write it down, and spread the word. Talking about your goals will spring them from your subconscious into your consciousness.&lt;/p&gt;&lt;p&gt;Phase 3: Adopt a Relentless Solution Focus - Anytime you are in the presence of adversity ask yourself, "What is one thing I can do that could make this better?"&lt;/p&gt;&lt;/div&gt;</description><author>Chester Grant</author><pubDate>Sat, 16 Dec 2023 17:46:11 GMT</pubDate><guid isPermaLink="true">https://www.chestergrant.com/summary-10-minute-toughness-by-jason-selk</guid></item><item><title>Horsey Gap and Horsey Windpump</title><link>https://sam.hooke.me/trip/2023/12/horsey-gap-and-horsey-windpump/</link><description>&lt;div id="map-main"&gt;&lt;/div&gt;




&lt;h2 id="seals-at-horsey-gap"&gt;Seals at Horsey Gap&lt;/h2&gt;
&lt;p&gt;We arrived at Horsey Gap at 12:45pm on a Saturday. There is a dedicated, paid car park, which is the only option for those who have come by car. The fee is reasonable, and although the car park is not very big, cars generally seem to leave frequently enough that you soon get a space.&lt;/p&gt;
&lt;p&gt;Following are some of my favourite photos I took of the seals and their pups. While most of the seal pups are down on the beach, a handful were up in the dunes. Some helpful volunteers make sure that everyone keeps a safe distance away so as not to disturb the pups. Additionally, guide ropes help ensure that people stick to the paths, which helps preserve the terrain.&lt;/p&gt;</description><author>Sam Hooke</author><pubDate>Sat, 16 Dec 2023 14:45:00 GMT</pubDate><guid isPermaLink="true">https://sam.hooke.me/trip/2023/12/horsey-gap-and-horsey-windpump/</guid></item><item><title>I don't know what i did last summer</title><link>https://newbeelearn.com/blog/i-dont-know-what-i-did-last-summer/</link><description>TLDR: How author forgot his own code, ended up wasting time and learned importance of documenting the code.</description><author>Welcome to my weblog on Newbeelearn</author><pubDate>Sat, 16 Dec 2023 14:26:23 GMT</pubDate><guid isPermaLink="true">https://newbeelearn.com/blog/i-dont-know-what-i-did-last-summer/</guid></item><item><title>Migrating existing page to hugo static site generator</title><link>https://newbeelearn.com/blog/landing-page-with-hugo/</link><description>TLDR: How to migrate existing site to Hugo</description><author>Welcome to my weblog on Newbeelearn</author><pubDate>Sat, 16 Dec 2023 14:26:23 GMT</pubDate><guid isPermaLink="true">https://newbeelearn.com/blog/landing-page-with-hugo/</guid></item><item><title>How to Use a Bash Script to Count Your Heroku Releases</title><link>https://williamhuster.com/count-production-heroku-releases/</link><description>&lt;p&gt;Our Creative Director asked me if I could estimate the number of production releases we have pushed so we could bost about it on &lt;a href="https://thinknimble.com"&gt;our website&lt;/a&gt;. I quickly figured out a strategy that would work using just the &lt;code class="language-plaintext highlighter-rouge"&gt;heroku apps&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;heroku releases&lt;/code&gt; commands, but realized I would need to draft a script to iterate and parse the outputs. I also knew it would be best to do this in bash to make use of those heroku-cli commands.&lt;/p&gt;

&lt;p&gt;I’m ashamed to say my bash is not strong, so I turned to ChatGPT 3.5 for help. Here’s a trimmed version of the exchange with ChatGPT.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Me:&lt;/strong&gt; Please write a bash script that helps me count how total production releases to Heroku. Procedure:&lt;/p&gt;

  &lt;ol&gt;
    &lt;li&gt;Calls &lt;code class="language-plaintext highlighter-rouge"&gt;heroku apps -A&lt;/code&gt; to list my Heroku apps&lt;/li&gt;
    &lt;li&gt;Finds all apps whose names contain the substring ‘prod’&lt;/li&gt;
    &lt;li&gt;For each app in #2, calls &lt;code class="language-plaintext highlighter-rouge"&gt;heroku releases -a {app_name}&lt;/code&gt;&lt;/li&gt;
    &lt;li&gt;Sums all the latest version numbers to get a total number of production releases&lt;/li&gt;
  &lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;ChatGPT&lt;/strong&gt; was pretty close on the first try. The overall structure of the script was correct, but it was not correctly parsing the latest release from the output of the &lt;code class="language-plaintext highlighter-rouge"&gt;heroku releases&lt;/code&gt; command. This was the command it drafted:&lt;/p&gt;

&lt;div class="language-bash highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nv"&gt;releases&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;heroku releases &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'^[0-9]+'&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 1 | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is very close, but it won’t yield any matches, because the regex used for &lt;code class="language-plaintext highlighter-rouge"&gt;grep&lt;/code&gt; is matching digits 0-9, but the release numbers are prefixed with a &lt;code class="language-plaintext highlighter-rouge"&gt;v&lt;/code&gt;, e.g. &lt;code class="language-plaintext highlighter-rouge"&gt;v55&lt;/code&gt;. The correct regex is therefore: &lt;code class="language-plaintext highlighter-rouge"&gt;'^v[0-9]+'&lt;/code&gt;. The variable name &lt;code class="language-plaintext highlighter-rouge"&gt;releases&lt;/code&gt; is also not entirely accurate and may indicate a “misunderstanding” of the goal. We’d like this line to give us just the latest release number. That said, the command is using &lt;code class="language-plaintext highlighter-rouge"&gt;head -n 1&lt;/code&gt; to get the latest release. So I decided to give ChatGPT an example of the output to see if it could correct the error.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Me:&lt;/strong&gt; This is close. There is just an issue parsing the latest release number from the result of the &lt;code class="language-plaintext highlighter-rouge"&gt;heroku releases&lt;/code&gt; command. Here is an example result:&lt;/p&gt;

  &lt;div class="language-plaintext highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;v88  Deploy b5e486a4                       dev@thinknimble.com  2023/12/15 03:33:29 -0500 (~ 12h ago)
v87  Set REDIS_URL config vars             dev@thinknimble.com  2023/12/15 03:11:25 -0500 (~ 12h ago)
v86  Deploy eb10b67c                       dev@thinknimble.com  2023/12/15 02:48:50 -0500 (~ 13h ago)
v85  Deploy 2ced23ee                       dev@thinknimble.com  2023/12/15 00:41:51 -0500 (~ 15h ago)
v84  Deploy 1f24c745                       dev@thinknimble.com  2023/12/13 02:29:13 -0500
v83  Deploy 58c7705c                       dev@thinknimble.com  2023/12/13 02:08:38 -0500
v82  Deploy 8b80ec50                       dev@thinknimble.com  2023/12/12 21:19:02 -0500
v81  Deploy bb148408                       dev@thinknimble.com  2023/12/12 19:51:02 -0500
v80  Set USE_REDIS config vars             dev@thinknimble.com  2023/12/12 18:47:41 -0500
v79  Set REDIS_URL config vars             dev@thinknimble.com  2023/12/12 18:47:33 -0500
v78  Set REDIS_URL config vars             dev@thinknimble.com  2023/12/12 18:44:43 -0500
v77  Set REDIS_URL config vars             dev@thinknimble.com  2023/12/12 18:36:27 -0500
v76  Update NEW_RELIC by newrelic          dev@thinknimble.com  2023/12/12 10:44:14 -0500
v75  Set AWS_S3_SECURE_URLS config vars    dev@thinknimble.com  2023/12/10 17:29:48 -0500
v74  Set AWS_S3_CUSTOM_DOMAIN config vars  dev@thinknimble.com  2023/12/10 17:29:32 -0500
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;So in this case, we want to parse out the number 88 and add that to &lt;code class="language-plaintext highlighter-rouge"&gt;total_releases&lt;/code&gt;. Can you regenerate the script with that in mind?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;ChatGPT&lt;/strong&gt; then regenerated the script and changed the line. This time the regex is correct, but we have a regression. For some reason, ChatGPT removed the &lt;code class="language-plaintext highlighter-rouge"&gt;head -n 1&lt;/code&gt; step. So this will give us a list of the release numbers of all releases in the output. I think it may have done this because I gave it a list of outputs above, instead of a single line.&lt;/p&gt;

&lt;div class="language-bash highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nv"&gt;latest_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;heroku releases &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'^v[0-9]+'&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/v//'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;One more nudge from me set this right:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Me:&lt;/strong&gt; Not quite. Instead of one &lt;code class="language-plaintext highlighter-rouge"&gt;latest_version&lt;/code&gt; this is giving me a list of all the versions. Try again?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And &lt;strong&gt;ChatGPT&lt;/strong&gt; corrected the line. Now the variable name &lt;code class="language-plaintext highlighter-rouge"&gt;latest_version&lt;/code&gt; is good and we are getting a single release number to add to &lt;code class="language-plaintext highlighter-rouge"&gt;total_releases&lt;/code&gt;. Nice!&lt;/p&gt;

&lt;div class="language-bash highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nv"&gt;latest_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;heroku releases &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'^v[0-9]+'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rV&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 1 | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/v//'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id="the-final-script"&gt;The Final Script&lt;/h2&gt;

&lt;p&gt;Here is the final script mainly authored by ChatGPT that will get a list of all production Heroku apps you have access to and sum up their latest release versions to give you a total number of all-time production deployments.&lt;/p&gt;

&lt;p&gt;In our case, the number of total production releases is over 2000! And that doesn’t even include apps on AWS or those we have shut down and transferred away to clients. So I’d guess out team has probably pushed to prod almost 5000 times over the past five years. A vanity metric, perhaps, but it’s still cool to think about.&lt;/p&gt;

&lt;div class="language-bash highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="c"&gt;# Step 1: List Heroku apps&lt;/span&gt;
&lt;span class="nv"&gt;apps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;heroku apps &lt;span class="nt"&gt;-A&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Step 2: Find apps with names containing 'prod'&lt;/span&gt;
&lt;span class="nv"&gt;prod_apps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$apps&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'prod'&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Step 3 and 4: Iterate over prod apps and get total releases&lt;/span&gt;
&lt;span class="nv"&gt;total_releases&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;span class="k"&gt;for &lt;/span&gt;app &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;$prod_apps&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nv"&gt;latest_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;heroku releases &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'^v[0-9]+'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rV&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 1 | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/v//'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$latest_version&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nv"&gt;total_releases&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;total_releases &lt;span class="o"&gt;+&lt;/span&gt; latest_version&lt;span class="k"&gt;))&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"App &lt;/span&gt;&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="s2"&gt; has version v&lt;/span&gt;&lt;span class="nv"&gt;$latest_version&lt;/span&gt;&lt;span class="s2"&gt; as the latest production release."&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"No production releases found for app &lt;/span&gt;&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="s2"&gt;."&lt;/span&gt;
    &lt;span class="k"&gt;fi
done

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Total production releases: &lt;/span&gt;&lt;span class="nv"&gt;$total_releases&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</description><author>William Huster</author><pubDate>Sat, 16 Dec 2023 13:30:00 GMT</pubDate><guid isPermaLink="true">https://williamhuster.com/count-production-heroku-releases/</guid></item><item><title>👨‍🎨 An Art Tour of Europe</title><link>https://www.austinatchley.xyz/posts/2023/12/an-art-tour-of-europe/</link><description>&lt;p&gt;While recovering from my second bout of COVID, I have been looking back through the pictures I took on my around-the-world trip this summer (&lt;a href="https://www.austinatchley.xyz/posts/2023/06/%EF%B8%8F-rtw-my-big-travel-plans-for-2023/"&gt;which you can read more about here&lt;/a&gt;). On the Europe leg of the trip, we were lucky enough to visit some of the best museums in the world, but we also found some inspiring art in unexpected places. Excluding famous works, here are some selected favorites:&lt;/p&gt;</description><author>Austin Atchley</author><pubDate>Sat, 16 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.austinatchley.xyz/posts/2023/12/an-art-tour-of-europe/</guid></item><item><title>(Suggested 📚) Turn the Ship Around!</title><link>https://er4hn.info/blog/2023.12.16-turn-the-ship-around/</link><description>Then Life Continued - Suggested Reading</description><author>er4hn</author><pubDate>Sat, 16 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://er4hn.info/blog/2023.12.16-turn-the-ship-around/</guid></item><item><title>SPHINCS+ - Step by Step</title><link>https://er4hn.info/blog/2023.12.16-sphincs_plus-step-by-step/</link><description>Then Life Continued</description><author>er4hn</author><pubDate>Sat, 16 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://er4hn.info/blog/2023.12.16-sphincs_plus-step-by-step/</guid></item><item><title>Exposure: Poisoned Water, Corporate Greed, and One Lawyer's Twenty-Year Battle against DuPont</title><link>https://olshansky.info/book/exposure/</link><description>Olshansky's review of Exposure: Poisoned Water, Corporate Greed, and One Lawyer's Twenty-Year Battle against DuPont by Robert Bilott</description><author>🦉 olshansky 🦁</author><pubDate>Sat, 16 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/book/exposure/</guid></item><item><title>Exposure: Poisoned Water, Corporate Greed, and One Lawyer's Twenty-Year Battle against DuPont</title><link>https://olshansky.info/book/exposure_dupont/</link><description>Olshansky's review of Exposure: Poisoned Water, Corporate Greed, and One Lawyer's Twenty-Year Battle against DuPont</description><author>🦉 olshansky 🦁</author><pubDate>Sat, 16 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/book/exposure_dupont/</guid></item><item><title>How to write a JSON Schema</title><link>https://benbrougher.tech/posts/json-schemas-tutorial/</link><description>How to write a JSON Schema and where they can be used</description><author>benbrougher.tech</author><pubDate>Fri, 15 Dec 2023 14:40:22 GMT</pubDate><guid isPermaLink="true">https://benbrougher.tech/posts/json-schemas-tutorial/</guid></item><item><title>The wilderness of Poggio Montironi</title><link>https://nicolaiarocci.com/the-wilderness-of-poggio-montironi/</link><description>&lt;p&gt;Yesterday was a beautiful, sunny, cold winter Sunday. I felt like going out and
enjoying nature, so I took a solo hike in the Foreste Casentinesi National Park,
about an hour&amp;rsquo;s drive from home.&lt;/p&gt;
&lt;p&gt;This one marks my very first technology-assisted hiking adventure. It may seem
weird for someone who&amp;rsquo;s been hiking for so long, is a notorious geek and is a
professional computer programmer to have never used technology before. A trail
map and sometimes a compass were all I was used to, and deliberately. I wanted
to avoid technology in this aspect of my life. I welcomed the orientation
challenges and superbly looked down at the crowds of phone-smartwatch-compulsive
hikers I met on the route. Alas, 2023 was the year I surrendered my motorcycling
habits to intercom systems and GPS navigation, and that spoiled me. In a couple
of situations yesterday, the app spared me some trouble by warning me of the
wrong direction I was going. Nothing major. I would&amp;rsquo;ve realized the error and
backtracked, but being these the shortest and coldest days of the year, I
appreciated not risking getting caught by dusk. Also, I found that I can turn on
voice-assisted navigation as I do on my motorcycle, and that&amp;rsquo;s nice (albeit
surreal - walking alone in the wilderness, miles from anybody, with a voice
coming out of nowhere and whispering when I should take turns on the trail): it
avoids looking at the cellphone all the time so I can stay focused on the
experience even more, I suspect, than before when the don&amp;rsquo;t-get-lost alarm bell
was constantly ringing in the back of my head.&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Fri, 15 Dec 2023 08:05:25 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/the-wilderness-of-poggio-montironi/</guid></item><item><title>Balm in GILead</title><link>https://blog.vito.nyc/posts/gil-balm/</link><description>&lt;p&gt;Python isn&amp;rsquo;t slow. &lt;a href="https://github.com/python/cpython/blob/97857ac0580057c3a4f75d34209841c81ee11a96/Python/ceval.c#L679"&gt;The core eval loop&lt;/a&gt;,
while slower than a JIT, is no slouch when it comes to dispatching bytecode.
There is no reason that business logic written in Python which orchestrates the
operation of highly optimized extension libraries should be a bottle neck.&lt;/p&gt;
&lt;aside class="collapsible"&gt;
  &lt;button class="collapse-button"&gt;
    &lt;span class="collapse-left"&gt;&lt;/span&gt;
    &lt;span class="collapse-center"&gt;
      &lt;span class="button-label"&gt;
        
          Time is precious, get to the point
        
      &lt;/span&gt;
    &lt;/span&gt;
    &lt;span class="collapse-right"&gt;&lt;/span&gt;
  &lt;/button&gt;
  &lt;div class="collapse-div"&gt;
    &lt;div class="collapse-inner"&gt;
      
      
      &lt;p&gt;&lt;a href="https://github.com/nickelpro/gilbench"&gt;Code and benchmarks are here&lt;/a&gt;, fair
winds and following seas.&lt;/p&gt;
&lt;p&gt;Also, hey, if you&amp;rsquo;re real busy because you have a job and you&amp;rsquo;re hiring,
I also would like a job. &lt;a href="https://vito.nyc/vito_gamberini_cv.pdf"&gt;Resume is here.&lt;/a&gt;&lt;/p&gt;</description><author>vito.nyc</author><pubDate>Fri, 15 Dec 2023 04:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.vito.nyc/posts/gil-balm/</guid></item><item><title>Lessons from implementing Hurl</title><link>https://ntietz.com/blog/lessons-from-implementing-hurl/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;I'm proud to announce that Hurl is officially released and done!
You can check out the docs on &lt;a href="https://hurl.wtf"&gt;hurl.wtf&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The language itself came out of an interesting question: Python &lt;em&gt;sometimes&lt;/em&gt; uses exceptions for control flow, so could we implement a language that eschews normal control flow and &lt;em&gt;only&lt;/em&gt; uses exceptions?
The answer is yes, and it produces a language that's less bad to use than I expected&lt;sup class="footnote-reference" id="fr-still-bad-tho-1"&gt;&lt;a href="https://ntietz.com/blog/lessons-from-implementing-hurl/#fn-still-bad-tho"&gt;[1]&lt;/a&gt;&lt;/sup&gt;!&lt;/p&gt;
&lt;p&gt;In the process of implementing it, I learned a lot.
Next year I'm going to try to make another language to learn about type systems, and that one should be more normal (but no promises).
Here are a few of my main takeaways from building Hurl.&lt;/p&gt;
&lt;h1 id="working-without-control-flow-is-fine"&gt;Working without control flow is... fine?&lt;/h1&gt;
&lt;p&gt;I thought it would be totally mind bending to work without ordinary control flow.
The first couple of programs &lt;em&gt;were&lt;/em&gt; mind bending but then you just learn the common patterns.
If you need to do an if-else, that's a &lt;code&gt;catch (true) ... catch (false)&lt;/code&gt;.
Looping is harder to wrap your head around but it's also not so bad.&lt;/p&gt;
&lt;p&gt;And the thing is, this is a general purpose programming language, so we can &lt;em&gt;build&lt;/em&gt; this control flow.
I ended up with a function called &lt;code&gt;if&lt;/code&gt; that takes a condition function and a body function as variables, and it runs those.
So you can write code like:&lt;/p&gt;
&lt;pre class="language-hurl " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-hurl"&gt;&lt;span&gt;if(func() {
&lt;/span&gt;&lt;span&gt;  hurl year == 2023;
&lt;/span&gt;&lt;span&gt;}, func() {
&lt;/span&gt;&lt;span&gt;  println(&amp;quot;Hurl was written in 2023!&amp;quot;);
&lt;/span&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It's not as clean as an &lt;code&gt;if&lt;/code&gt; in reasonable languages, but it's also cleaner than I expected.
Part of this is also because I used dynamic scope, not lexical scope, so functions can operate more easily over outer scopes, but it would be doable either way with minor changes.&lt;/p&gt;
&lt;p&gt;This has me really excited to explore things like &lt;a href="https://en.wikipedia.org/wiki/Assembly_language"&gt;assembly&lt;/a&gt;.
I've been really intimidated by it my entire career, feeling inadequate and all the usual impostor feelings.
But now I can see concretely that eschewing normal control flow won't be a problem in itself&lt;sup class="footnote-reference" id="fr-assembly-1"&gt;&lt;a href="https://ntietz.com/blog/lessons-from-implementing-hurl/#fn-assembly"&gt;[2]&lt;/a&gt;&lt;/sup&gt;.
I've had &lt;a href="http://www.riscvbook.com/"&gt;The RISC-V Reader&lt;/a&gt; on my desk for a while and now it seems more approachable.&lt;/p&gt;
&lt;p&gt;An unexpected lesson for me, but I'll take it.
So expect to see some RISC-V content next year!&lt;/p&gt;
&lt;h1 id="all-the-nice-things-are-so-hard"&gt;All the nice things are so hard&lt;/h1&gt;
&lt;p&gt;My ambitions for Hurl were larger than my skills and time allowed for, and I had to pare it back.
The things that got cut were any of the tools that would make the language fairly nice to use, and error messages unfortunately went by the wayside.&lt;/p&gt;
&lt;p&gt;I realized this would be harder than expected when I started &lt;a href="https://ntietz.com/blog/writing-basic-code-formatter/"&gt;writing the formatter&lt;/a&gt; and then I started to rethink some of the other ambitions I had.&lt;/p&gt;
&lt;p&gt;In a future language I &lt;em&gt;will&lt;/em&gt; come back to some of these things.
I'd love to write a slightly more sophisticated formatter that makes things a little prettier (though having one at all is an accomplishment I'm proud of).
And I am really interested in exploring writing a &lt;a href="https://en.wikipedia.org/wiki/Language_Server_Protocol"&gt;language server&lt;/a&gt; for a homemade language next year, but this year I just could not work on it.&lt;/p&gt;
&lt;p&gt;The big thing I have a lot of appreciation for now is the quality of error messages and debugging support in other languages.
Generating error messages that point to where the error happened in the executing program requires that you track all the line information at run time!
And that means you have to design it in from the beginning.
Guess who didn't realize that and made some mistakes that would've been trouble to fix later?&lt;/p&gt;
&lt;p&gt;Yeah, all those things that make a language nice to use are just a &lt;em&gt;lot&lt;/em&gt; of work.
And my promise (to myself, to you) is that my next language will work at these and the goal will to be fairly pleasant to use as far as educational language projects go.&lt;/p&gt;
&lt;h1 id="writing-your-own-parser-tokenizer-can-makes-sense"&gt;Writing your own parser/tokenizer can makes sense&lt;/h1&gt;
&lt;p&gt;After working through &lt;a href="https://craftinginterpreters.com/"&gt;Crafting Interpreters&lt;/a&gt; in 2022&lt;sup class="footnote-reference" id="fr-crafting-interpreters-1"&gt;&lt;a href="https://ntietz.com/blog/lessons-from-implementing-hurl/#fn-crafting-interpreters"&gt;[3]&lt;/a&gt;&lt;/sup&gt; where we implemented the tokenizer and parser from hand, I left with the impression that this was an intentional choice for instruction but perhaps not how we'd do it for real.
Since then I've learned that a great many languages &lt;em&gt;do&lt;/em&gt; roll their own tokenizers and parsers, and I still wasn't sure why.&lt;/p&gt;
&lt;p&gt;I used &lt;a href="https://pest.rs"&gt;pest&lt;/a&gt; to generate my parser for Hurl, and in a lot of ways it was pleasant to use.
But on the other hand, it felt fairly restrictive and it was a lot to learn.
In the end, I'm not sure that I saved time over writing my own parser.&lt;/p&gt;
&lt;p&gt;If you write your own tokenizer and parser, you get full control and you avoid adding another dependency.
They're also not that difficult to write (but there are a lot of details to get right, so they're tricky to get fully correct).&lt;/p&gt;
&lt;p&gt;I would probably use &lt;code&gt;pest&lt;/code&gt; again for a real project&lt;sup class="footnote-reference" id="fr-am-i-the-work-pest-1"&gt;&lt;a href="https://ntietz.com/blog/lessons-from-implementing-hurl/#fn-am-i-the-work-pest"&gt;[4]&lt;/a&gt;&lt;/sup&gt; and it's used by some quite respectable projects like &lt;a href="https://github.com/rust-lang/mdBook"&gt;mdbook&lt;/a&gt;.
But for for my next language, I'm going to write my own tokenizer and parser again.
It's pretty fun, I don't think it'll cost me much time, and yeah why not?&lt;/p&gt;
&lt;h1 id="relying-on-the-os-stack-was-a-big-mistake"&gt;Relying on the OS stack was a big mistake&lt;/h1&gt;
&lt;p&gt;I implemented a tree-walk interpreter, and recursion&lt;sup class="footnote-reference" id="fr-dont-blame-rc-1"&gt;&lt;a href="https://ntietz.com/blog/lessons-from-implementing-hurl/#fn-dont-blame-rc"&gt;[5]&lt;/a&gt;&lt;/sup&gt; is the only way you get looping, which results in a problem: as you loop, you push onto the stack and you get stack overflows.
And it's the &lt;em&gt;OS&lt;/em&gt; provided stack for the &lt;em&gt;interpreter&lt;/em&gt; which you end up blowing, so this isn't something we can just patch in Hurl itself.
This isn't something I thought through critically before deciding to do a tree-walk interpreter.
I knew that these are limited in some ways, but forgot how it would impact Hurl.&lt;/p&gt;
&lt;p&gt;One solution here would be to migrate to a bytecode interpreter.
That's a big project and would be a rewrite of the whole interpreter, so it's not in the cards.
There might also be a way to optimize out some of the recursion here and create a loop from something tail recursive, but I don't know.&lt;/p&gt;
&lt;p&gt;Another solution would be to add a new language construct.
I'm not sure which language construct would help us out here, so it's an undetermined thing at the moment.&lt;/p&gt;
&lt;p&gt;In the future, I'll make sure to account for this from the beginning and use a bytecode interpreter approach, or transpile to another language.&lt;/p&gt;
&lt;h1 id="licenses-can-be-fun"&gt;Licenses can be fun&lt;/h1&gt;
&lt;p&gt;Software licenses don't have a reputation for being particularly, uh, exciting&lt;sup class="footnote-reference" id="fr-license-love-1"&gt;&lt;a href="https://ntietz.com/blog/lessons-from-implementing-hurl/#fn-license-love"&gt;[6]&lt;/a&gt;&lt;/sup&gt;.
But they don't have to be boring!
They can be an opportunity for play, too.&lt;/p&gt;
&lt;p&gt;Part of creating Hurl is art, and the license choice is a big part of that.
The &lt;em&gt;best&lt;/em&gt; license choice would have been an OSS license and then later do a rug pull and &lt;a href="https://www.hashicorp.com/blog/hashicorp-adopts-business-source-license"&gt;relicense as BSL&lt;/a&gt;, but that would imply that this project would get any attention.
The &lt;em&gt;second&lt;/em&gt; best license choice was to lean into my values intentionally.
I ultimately decided to pick a license that would:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;permit funny outcomes&lt;/li&gt;
&lt;li&gt;allow educational use&lt;/li&gt;
&lt;li&gt;reflect my morality and ethics&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And to do this I settled on not just a license.
Not dual licenses.
No, that would make sense.&lt;/p&gt;
&lt;p&gt;Hurl is &lt;em&gt;triple&lt;/em&gt; licensed.&lt;/p&gt;
&lt;p&gt;You can choose which of the licenses applies.
You've got the standard AGPL-3.0 (no "or later" here, I don't want to be bound to the FSF).
You've also got the choice to buy a commercial license (serious inquiries only 😉).
Or you can use it under GAL-1.0 (the Gay Agenda License 1.0).
Here's that license, in its full glory&lt;sup class="footnote-reference" id="fr-anti-license-1"&gt;&lt;a href="https://ntietz.com/blog/lessons-from-implementing-hurl/#fn-anti-license"&gt;[7]&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;pre class="language-text " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-text"&gt;&lt;span&gt;# Gay Agenda License - 1.0
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;Copyright (c) 2023 Nicole Tietz-Sokolskaya &amp;lt;me@ntietz.com&amp;gt;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;Permission is hereby granted, free of charge, to any person obtaining a copy
&lt;/span&gt;&lt;span&gt;of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal
&lt;/span&gt;&lt;span&gt;in the Software without restriction, including without limitation the rights
&lt;/span&gt;&lt;span&gt;to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
&lt;/span&gt;&lt;span&gt;copies of the Software, and to permit persons to whom the Software is
&lt;/span&gt;&lt;span&gt;furnished to do so, subject to the following conditions:
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;  - The above copyright notice and this permission notice shall be included in
&lt;/span&gt;&lt;span&gt;    all copies or substantial portions of the Software.
&lt;/span&gt;&lt;span&gt;  - The grantee shall actively support rights for all LGBTQ+ people, respecting
&lt;/span&gt;&lt;span&gt;    their gender identities.
&lt;/span&gt;&lt;span&gt;  - The grantee shall say &amp;quot;be gay, do crime&amp;quot; at least once during use of the
&lt;/span&gt;&lt;span&gt;    software.
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;The license is immediately revoked if the grantee supports restricting the
&lt;/span&gt;&lt;span&gt;rights of LGBTQ+ people.
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;If the grantee is found to not have said &amp;quot;be gay, do crime&amp;quot; during use of the
&lt;/span&gt;&lt;span&gt;software, the grantee has thirty (30) days to remediate this violation without
&lt;/span&gt;&lt;span&gt;loss of the license. If it is not remediated, then the grantee's grants via
&lt;/span&gt;&lt;span&gt;this license are premanently retracted.
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
&lt;/span&gt;&lt;span&gt;IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
&lt;/span&gt;&lt;span&gt;FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
&lt;/span&gt;&lt;span&gt;AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
&lt;/span&gt;&lt;span&gt;LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
&lt;/span&gt;&lt;span&gt;OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
&lt;/span&gt;&lt;span&gt;SOFTWARE.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Feel free to use this software for your own projects, with a few caveats.
It was modified from the MIT license, so most of it is boilerplate, with a couple of additions to it.
The main thing to note if you do for some reason decide to use this license is that it is &lt;em&gt;not tested&lt;/em&gt; and I would probably be surprised if it's enforceable.
No lawyer has been involved or harmed in its creation.&lt;/p&gt;
&lt;p&gt;At the end of the day, though, what license &lt;em&gt;is&lt;/em&gt; enforceable if you don't have the money to fight Amazon on it?&lt;/p&gt;
&lt;h1 id="playing-is-very-educational"&gt;Playing is very educational&lt;/h1&gt;
&lt;p&gt;This was the biggest takeaway.
It's not new to me, and I've written before that you should &lt;a href="https://ntietz.com/blog/write-more-useless-software/"&gt;write more "useless" software&lt;/a&gt;.
It was a great reminder of the joy and learning that can come from a long project that's &lt;em&gt;just&lt;/em&gt; for fun and that has no practical value.&lt;/p&gt;
&lt;p&gt;Along the way, I had a lot of fun.
I'm not sure if I made friends or enemies.
And I sure did learn a &lt;em&gt;lot&lt;/em&gt;.
Some of what I learned, I can apply at work starting this week&lt;sup class="footnote-reference" id="fr-jessica-1"&gt;&lt;a href="https://ntietz.com/blog/lessons-from-implementing-hurl/#fn-jessica"&gt;[8]&lt;/a&gt;&lt;/sup&gt;!
And some of it is just added context for why certain things are hard, and makes me more deeply appreciate the tools that our dear language teams give us ❤️.&lt;/p&gt;
&lt;p&gt;Go forth and write some playful code!&lt;/p&gt;
&lt;hr /&gt;
&lt;hr /&gt;&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-still-bad-tho"&gt;
&lt;p&gt;It is still &lt;em&gt;pretty bad&lt;/em&gt; to use, though. &lt;a href="https://ntietz.com/blog/lessons-from-implementing-hurl/#fr-still-bad-tho-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-assembly"&gt;
&lt;p&gt;In fact, assembly might get us a little closer to ordinary control flow than Hurl does. &lt;a href="https://ntietz.com/blog/lessons-from-implementing-hurl/#fr-assembly-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-crafting-interpreters"&gt;
&lt;p&gt;&lt;em&gt;Highly&lt;/em&gt; recommend this magnificent tome if you want to learn from a professional language person. And his illustrations are beautiful! &lt;a href="https://ntietz.com/blog/lessons-from-implementing-hurl/#fr-crafting-interpreters-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-am-i-the-work-pest"&gt;
&lt;p&gt;I do have something brewing at work that will possibly use it, or &lt;a href="https://docs.rs/nom/latest/nom/"&gt;nom&lt;/a&gt;. &lt;a href="https://ntietz.com/blog/lessons-from-implementing-hurl/#fr-am-i-the-work-pest-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-dont-blame-rc"&gt;
&lt;p&gt;Despite the relation of names, &lt;a href="https://recurse.com"&gt;the Recurse Center&lt;/a&gt; has no fault in the creation of Hurl. The people there are quite lovely, and most don't implement languages like Hurl! &lt;a href="https://ntietz.com/blog/lessons-from-implementing-hurl/#fr-dont-blame-rc-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-license-love"&gt;
&lt;p&gt;To all my lawyer friends out there who are reading this and vehemently disagree, reach out to me, would love to chat. &lt;a href="https://ntietz.com/blog/lessons-from-implementing-hurl/#fr-license-love-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-anti-license"&gt;
&lt;p&gt;This license was inspired by boringcactus's post &lt;a href="https://www.boringcactus.com/2021/09/29/anti-license-manifesto.html"&gt;An Anti-License Manifesto&lt;/a&gt;. &lt;a href="https://ntietz.com/blog/lessons-from-implementing-hurl/#fr-anti-license-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-jessica"&gt;
&lt;p&gt;Jessica, don't worry, I'm not going to actually &lt;em&gt;use&lt;/em&gt; Hurl at work. &lt;a href="https://ntietz.com/blog/lessons-from-implementing-hurl/#fr-jessica-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Fri, 15 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/lessons-from-implementing-hurl/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Offline developer docs: Dash docsets via Zeal</title><link>https://jiby.tech/post/offline-dev-docs-zeal-dash-docsets/</link><description>&lt;p&gt;Since my earliest university days, I&amp;rsquo;ve spent a lot of time trying to get better
at computers, immersing myself in the world of Linux, and generally
improving my software development skills.&lt;/p&gt;
&lt;p&gt;One of the aspects that was the most helpful to help me grow was attempting to
replicate my normal dev workflow, but with less: Learn to operate on Linux not
Windows, then learn to do dev via on the terminal instead of GUIs, and
eventually learn to do the same but offline, without any network calls.&lt;/p&gt;</description><author>Jiby's toolbox</author><pubDate>Fri, 15 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jiby.tech/post/offline-dev-docs-zeal-dash-docsets/</guid></item><item><title>Lahaina four months after the fire</title><link>https://billsaysthis.com/2023/12/14/lahaina-four-months-after-the-fire/</link><description>If you want an overview of how things are in Lahaina four months after the fire, check out Jesse Wald&amp;#8217;s video. TL;DR: The EPA just completed the hazardous materials removal project and now the main debris removal will start and take about a year. The devastation is just so visible now, makes me so sad. &amp;#8230; &lt;a class="more-link" href="https://billsaysthis.com/2023/12/14/lahaina-four-months-after-the-fire/"&gt;Continue reading &lt;span class="screen-reader-text"&gt;Lahaina four months after the&amp;#160;fire&lt;/span&gt;&lt;/a&gt;</description><author>BillSaysThis</author><pubDate>Thu, 14 Dec 2023 22:40:21 GMT</pubDate><guid isPermaLink="true">https://billsaysthis.com/2023/12/14/lahaina-four-months-after-the-fire/</guid></item><item><title>Phobias Good and Bad</title><link>https://synystron.substack.com/p/phobias-good-and-bad</link><description>People should be more into the active *application* of "jerk-o-phobia" than be obsessed with many of the other so-called (and arbitrary!) phobias they like to cite.</description><author>Synystron Synlogica</author><pubDate>Thu, 14 Dec 2023 20:16:11 GMT</pubDate><guid isPermaLink="true">https://synystron.substack.com/p/phobias-good-and-bad</guid></item><item><title>hashsets in go</title><link>https://evilcookie.de/hashsets-in-go.html</link><description/><author>blog</author><pubDate>Thu, 14 Dec 2023 15:15:50 GMT</pubDate><guid isPermaLink="true">https://evilcookie.de/hashsets-in-go.html</guid></item><item><title>How I learnt to run my own personal AI</title><link>https://ilearnt.com/blog/personalai/</link><description>&lt;p&gt;By now most people have tried or at least heard of ChatGPT. It is now relatively easy to run your own AI instance on your own machine.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Thu, 14 Dec 2023 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/personalai/</guid></item><item><title>How Many Hobbits?</title><link>https://nicolaiarocci.com/how-many-hobbits/</link><description>&lt;blockquote&gt;
&lt;p&gt;For those who don’t know me, I’m a demographer. I study population. And my first love in fantasy was, of course, Middle Earth. How many people live in Middle Earth? Being a demographer, I was mainly interested in the data side of things.&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Tolkien is frustratingly vague about population. He almost never gives us estimates of settlement sizes, and many of the larger metropolises of Middle Earth (like Pelargir) never actually appear on the page. Sizable armies make frequent appearances, yet because his adventurers almost exclusively traverse the wilds of Middle Earth, we rarely see where those soldiers are coming from.&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Thu, 14 Dec 2023 08:05:25 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/how-many-hobbits/</guid></item><item><title>The Danger of Metrics</title><link>https://zacs.site/blog/the-danger-of-metrics.html</link><description>&lt;p&gt;I got an email a few weeks ago about my article &lt;a href="https://mwi.westpoint.edu/operational-metrics-the-next-step-in-the-evolution-of-defensive-cyberspace-operations/"&gt;&lt;em&gt;Operational Metrics: The Next Step in the Evolution of Defensive Cyberspace Operations&lt;/em&gt;&lt;/a&gt;&lt;sup id="fnref1"&gt;&lt;a href="#fn1" rel="footnote"&gt;1&lt;/a&gt;&lt;/sup&gt; from a researcher studying the same problem. We talked about my article, &lt;a href="https://zacs.site/blog/soc-metrics-part-i.html"&gt;some&lt;/a&gt; of &lt;a href="https://zacs.site/blog/soc-metrics-part-ii.html"&gt;the other&lt;/a&gt; writing &lt;a href="https://zacs.site/blog/soc-metrics-part-iii.html"&gt;I have&lt;/a&gt; published &lt;a href="https://zacs.site/blog/individual-versus-program-soc-metrics.html"&gt;on this topic&lt;/a&gt;, and and then something I briefly touched on in my &lt;em&gt;SOC Metrics&lt;/em&gt; series: the dangers of metrics.&lt;/p&gt;
                &lt;p&gt;&lt;a href="https://zacs.site/blog/the-danger-of-metrics.html"&gt;Permalink.&lt;/a&gt;&lt;/p&gt;</description><author>Zac Szewczyk</author><pubDate>Thu, 14 Dec 2023 03:32:15 GMT</pubDate><guid isPermaLink="true">https://zacs.site/blog/the-danger-of-metrics.html</guid></item><item><title>Pinecone &amp;amp; Pulumi webinar: Taking AI apps to production</title><link>https://zackproser.com/videos/pinecone-pulumi-webinar-1</link><description>I co-hosted a webinar with Pulumi about productionizing AI applications</description><author>Zachary Proser</author><pubDate>Thu, 14 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/videos/pinecone-pulumi-webinar-1</guid></item><item><title>Malapropism</title><link>https://studiofreya.org/words/malapropism/</link><description>&lt;ul&gt;
&lt;li&gt;Ludicrous misuse of a word, especially by confusion with one of similar sound.&lt;/li&gt;
&lt;li&gt;An example of such misuse.&lt;/li&gt;
&lt;li&gt;The act or habit of misapplying words through an ambition to use fine language.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;A malapropism (also called a malaprop, acyrologia, or Dogberryism) is the use of an incorrect word in place of a word with a similar sound, either unintentionally or for comedic effect, resulting in a nonsensical, often humorous utterance.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Malapropism"&gt;https://en.wikipedia.org/wiki/Malapropism&lt;/a&gt;&lt;/p&gt;</description><author>Studiofreya SSG Site</author><pubDate>Thu, 14 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://studiofreya.org/words/malapropism/</guid></item><item><title>Quickdraw Hero</title><link>https://backendhance.com/blog/2023/quickdraw-hero/</link><description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;h3 id="beim-team-einer-meiner-kunden-habe-ich-den-spitznamen-revolver-held-bekommen"&gt;
&lt;a class="anchor" href="#beim-team-einer-meiner-kunden-habe-ich-den-spitznamen-revolver-held-bekommen"&gt;Beim Team einer meiner Kunden habe ich den Spitznamen &amp;ldquo;Revolver-Held&amp;rdquo; bekommen.&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Weil ich so schnell schieße.&lt;/p&gt;
&lt;p&gt;&lt;img alt="revolver.gif" src="https://backendhance.com/blog/2023/quickdraw-hero/revolver-held.gif" /&gt;&lt;/p&gt;
&lt;p&gt;Den Ruf bekam ich relativ schnell vom Team. Zu dem Zeitpunkt war er durchaus despektierlich gemeint.&lt;/p&gt;
&lt;h3 id="das-team-war-mein-tempo-nicht-gewohnt"&gt;
&lt;a class="anchor" href="#das-team-war-mein-tempo-nicht-gewohnt"&gt;Das Team war mein Tempo nicht gewohnt.&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Es hatte es sich gemütlich gemacht. Die Software ist seit mehr als 10 Jahren in Entwicklung. Und über die Zeit haben sich hohe technische Schulden angesammelt. Das war ein schleichender Prozess.&lt;/p&gt;</description><author>Backendhance</author><pubDate>Thu, 14 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/quickdraw-hero/</guid></item><item><title>Running Python on AWS Lambda, Revisited</title><link>https://www.keithrpetersen.com/blog/aws-lambda-python-custom-runtime-revisited/</link><description>&lt;p&gt;It’s been almost a year since &lt;a href="https://www.keithrpetersen.com/blog/python-311-aws-lambda-custom-runtime/"&gt;I got Python 3.10 and 3.11 running on AWS Lambda&lt;/a&gt; before official runtimes for those versions existed. Amazon has since caught up with the Python release cycle, and now has runtimes for those versions, while a 3.12 runtime release is imminent.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.keithrpetersen.com/blog/aws-lambda-python-custom-runtime-revisited/"&gt;Read More&lt;/a&gt;&lt;/p&gt;</description><author>Blog - Keith R. Petersen</author><pubDate>Thu, 14 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.keithrpetersen.com/blog/aws-lambda-python-custom-runtime-revisited/</guid></item><item><title>Ultimate Beneficial Ownership is Dangerous</title><link>https://omarabid.com/beneficial-ownership/</link><description>Examining how transparency laws can affect the balance of power.</description><author>Omar Abid - Personal Blog</author><pubDate>Thu, 14 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://omarabid.com/beneficial-ownership/</guid></item><item><title>Learning to ride</title><link>https://bowendwelle.substack.com/p/learning-to-ride</link><description>The three-mile circuit around the docks where my floating home is tied up takes about an hour. I make my way up to the end of each one and wheel around to the next, sucking in the salt air and the winter light that slants in over the headlands, checking the minus tide, the mud exposed in the flats, listening to the birds out searching for grub, giving each neighbor a nod and a smile as I march along.Passing though the shed at the entrance of Issaquah, I eye the giveaway shelf and snag a secondhand issue of The New Yorker, the front-cover illustration of a family gathered around the holiday table, every face turned screenwards, scrolling.</description><author>An Ordinary Disaster</author><pubDate>Thu, 14 Dec 2023 00:12:12 GMT</pubDate><guid isPermaLink="true">https://bowendwelle.substack.com/p/learning-to-ride</guid></item><item><title>Dwell and the Tools for Thought Hackathon</title><link>https://www.swyx.io/dwell-and-the-tools-for-thought-hackathon</link><description>I worked on a little hackathon project at AGI House with Sasha.  We won!</description><author>swyx's site RSS Feed</author><pubDate>Wed, 13 Dec 2023 22:47:31 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/dwell-and-the-tools-for-thought-hackathon</guid></item><item><title>Riding the Storm Out</title><link>https://bastian.rieck.me/blog/2023/storm/</link><description>&lt;p&gt;After a successful local meetup for the &lt;a href="https://logconference.org/"&gt;&amp;lsquo;Learning on Graphs&amp;rsquo; Conference&lt;/a&gt;,
I found myself almost stranded away from home. It had already been
snowing a little bit&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; over the previous evening, but upon waking up,
I was greeted by a lot of notifications concerning cancelled trains, flights,
and the like. A quick glance outside showed that more snow had fallen
over night, resulting in a non-Euclidean transformation of the city.
Where previously ambient and geodesic distance were at least correlated,
the snow had created a metric with singularities that might even stump
a Perelman. Public transport was
stopped for the time being. Walking was out of the question.&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;It looked like I might not reach home after all, but I had a strong
incentive to do so since my wife was home alone with our two-month old
baby. Fortunately, I love improvising and thought that the situation,
while not exactly optimal, could also be much, much worse. I thus walked into
the nearest hotel and pretended to be a guest, trying to order a taxi
into the inner city. The reason for this non-obvious destination was
that I suspected that the airport would be overwhelmed or close to being
so anyway in a couple of hours, whereas there might be a chance for
trains to run at least in the rough direction of &amp;lsquo;home.&amp;rsquo; Seeing taxi
after taxi leave for the airport, I started approaching some other
stranded guests who were putting their hopes into the train system.
Together, we got a taxi van that was willing to drive us into the city.&lt;/p&gt;
&lt;p&gt;Along the way, I briefly discussed travel plans with my fellow
travellers; upon checking their itinerary, I found that we all shared
a rough direction but, alas, no trains were running any more at this
point. I thus made the, somewhat jocular, proposition that we should
just drive to Zurich. Much to my surprise, this suggestion was taken up
by two of my fellow travellers, both execs in a highly-competitive
industry. They were motivated to get home as well, home being Nice in
this case, and, luckily for me, extremely experienced in such mishaps.
Indeed, one of them introduced his job as being a troubleshooter.
Our troubleshooter was able to negotiate successfully with the driver,
while I served as the translator, and then we were on our way. This was
a lot easier than I anticipated!&lt;/p&gt;
&lt;p&gt;During the drive we mostly encountered the road in good condition, with
some minor traffic jams. Next to the beautiful winter
landscape&amp;mdash;everything can be beautiful if it does not pose an immediate
inconvenience or threat to one&amp;rsquo;s own plans&amp;mdash;the thing I remember most
vividly are our conversations. While not &lt;em&gt;exactly&lt;/em&gt; like the Canterbury Tales,
the second manager, S., turned out to be an outstanding
conversationalist, engaging everyone of our small fellowship and drawing
some quite interesting stories out.&lt;/p&gt;
&lt;p&gt;He also shared some lessons from his long career, which struck a chord
in me:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;When you are in a problematic situation with imprecise knowledge, try
to move as fast as possible in any direction that strikes you as
better. You can always adjust course along the way.&lt;/li&gt;
&lt;li&gt;The fact that we can charter a taxi at no small cost without batting
an eye makes us already highly privileged in comparison to most other
human beings.&lt;/li&gt;
&lt;li&gt;Our biggest inconvenience is getting home during snowfall; later on,
this will make a good story. Many others are fleeing bad economic or
climate conditions and might not face such a happy end.&lt;/li&gt;
&lt;li&gt;&amp;lsquo;Ask and you shall receive:&amp;rsquo; We would have never gotten our little
fellowship together if we had not asked and improvised.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This was a good trip and, indeed, a little bit of an adventure. I am
grateful for the lessons and the shift in perspective. Until next time,
stay safe in your travels!&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Meaning that all walkways and streets were slightly powdered but
no more.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;I am a pedestrian by nature, but trudging about 20 kilometres
through the snow to reach the main station from the conference site
did not seem like a viable option.&amp;#160;&lt;a class="footnote-backref" href="#fnref:2"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>Ecce Homology on Bastian Grossenbacher Rieck's personal homepage</author><pubDate>Wed, 13 Dec 2023 22:00:00 GMT</pubDate><guid isPermaLink="true">https://bastian.rieck.me/blog/2023/storm/</guid></item><item><title>Trader Joe's Parking Lot</title><link>https://josh.works/trader-joes-parking</link><description>&lt;p&gt;Hey Trader Joe’s,&lt;/p&gt;

&lt;p&gt;This is a bit of an open letter, inspired by a recent visit to the local Trader Joe’s. I just moved to this part of Denver, and now for the first time am living within like a 3 minute scoot of a Trader Joe’s.&lt;/p&gt;

&lt;p&gt;I know that some people like to complain about parking at TJs. (Can I call it that? Trader Joe’s -&amp;gt; TJ’s?)&lt;/p&gt;

&lt;p&gt;Here’s a sampling from Reddit: &lt;a href="https://www.reddit.com/r/traderjoes/search/?q=parking"&gt;https://www.reddit.com/r/traderjoes/search/?q=parking&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lots of people mis-understand parking lots, and so no matter what happens, someone’s going to complain about something. I sort of love that Trader Joe’s has smallish parking lots, and would love to scheme to make them &lt;em&gt;even smaller&lt;/em&gt;, while making the users of the parking lot and visitor’s to TJ &lt;em&gt;way happier&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This is something that can be done. It is already done in certain ways around the world. Different countries have common, accepted solutions to different portions of this “parking lot problem”. I’d like to work with TJ’s to cobble together an off-the-shelf solution to all/most of the issues.&lt;/p&gt;

&lt;p&gt;Obviously, there’s a lot of money to save, hassle to reduce, delight to be had, money to be made, by better managing business-critical capital, and parking lots are critical to the functioning of businesses.&lt;/p&gt;

&lt;p&gt;Lets talk about it.&lt;/p&gt;

&lt;p&gt;Email me, we can email/phone/IRL discuss it. &lt;code class="language-plaintext highlighter-rouge"&gt;joshthompson@hey.com&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;❤️,&lt;/p&gt;

&lt;p&gt;- Josh&lt;/p&gt;

&lt;h2 id="tiktok-footage-of-a-local-tjs-parking-lot"&gt;Tiktok footage of a local TJ’s parking lot&lt;/h2&gt;

&lt;p&gt;Here’s footage to whet the appetite.&lt;/p&gt;

&lt;p&gt;Sometimes embeds show up strangely, so here’s direct links. I recommend clicking both:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href="https://www.tiktok.com/@josh_exists/video/7312191848838286638"&gt;timelaps overhead shot (one)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.tiktok.com/@josh_exists/video/7312160766151593262"&gt;timelapse overhead shot(two)&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote cite="https://www.tiktok.com/@josh_exists/video/7312191848838286638" class="tiktok-embed"&gt; &lt;section&gt; &lt;a href="https://www.tiktok.com/@josh_exists?refer=embed" target="_blank" title="@josh_exists"&gt;@josh_exists&lt;/a&gt; lots to appreciate here. the audio is great I have thoughts on parking lots. I see them as hidden sources of abundance, currently not allowed to function as well as they might. a little love goes a long way. &lt;a href="https://www.tiktok.com/tag/fyp?refer=embed" target="_blank" title="fyp"&gt;#fyp&lt;/a&gt; &lt;a href="https://www.tiktok.com/tag/dji?refer=embed" target="_blank" title="dji"&gt;#dji&lt;/a&gt; &lt;a href="https://www.tiktok.com/tag/traderjoes?refer=embed" target="_blank" title="traderjoes"&gt;#traderjoes&lt;/a&gt; &lt;a href="https://www.tiktok.com/tag/toyota?refer=embed" target="_blank" title="toyota"&gt;#toyota&lt;/a&gt; &lt;a href="https://www.tiktok.com/tag/ford?refer=embed" target="_blank" title="ford"&gt;#ford&lt;/a&gt; &lt;a href="https://www.tiktok.com/tag/denver?refer=embed" target="_blank" title="denver"&gt;#denver&lt;/a&gt; &lt;a href="https://www.tiktok.com/tag/colorado?refer=embed" target="_blank" title="colorado"&gt;#colorado&lt;/a&gt; &lt;a href="https://www.tiktok.com/tag/timelapse?refer=embed" target="_blank" title="timelapse"&gt;#timelapse&lt;/a&gt; &lt;a href="https://www.tiktok.com/tag/carlessdriver?refer=embed" target="_blank" title="carlessdriver"&gt;#carlessdriver&lt;/a&gt; &lt;a href="https://www.tiktok.com/tag/driverlesscar?refer=embed" target="_blank" title="driverlesscar"&gt;#driverlesscar&lt;/a&gt; &lt;a target="_blank" title="♬ Final Speech - From "&gt;♬ Final Speech - From "The Great Dictator" - Charlie Chaplin&lt;/a&gt; &lt;/section&gt; &lt;/blockquote&gt;


&lt;blockquote cite="https://www.tiktok.com/@josh_exists/video/7312160766151593262" class="tiktok-embed"&gt; &lt;section&gt; &lt;a href="https://www.tiktok.com/@josh_exists?refer=embed" target="_blank" title="@josh_exists"&gt;@josh_exists&lt;/a&gt; I'm gonna send this to someone at trader Joe's. I'll explain the book 'the high cost of free parking'. &lt;a href="https://www.tiktok.com/tag/traderjoes?refer=embed" target="_blank" title="traderjoes"&gt;#traderjoes&lt;/a&gt; &lt;a href="https://www.tiktok.com/tag/parking?refer=embed" target="_blank" title="parking"&gt;#parking&lt;/a&gt; &lt;a href="https://www.tiktok.com/tag/drone?refer=embed" target="_blank" title="drone"&gt;#drone&lt;/a&gt; &lt;a href="https://www.tiktok.com/tag/dji?refer=embed" target="_blank" title="dji"&gt;#dji&lt;/a&gt; &lt;a href="https://www.tiktok.com/tag/timelapse?refer=embed" target="_blank" title="timelapse"&gt;#timelapse&lt;/a&gt; &lt;a href="https://www.tiktok.com/tag/denver?refer=embed" target="_blank" title="denver"&gt;#denver&lt;/a&gt; &lt;a href="https://www.tiktok.com/tag/colorado?refer=embed" target="_blank" title="colorado"&gt;#colorado&lt;/a&gt; &lt;a href="https://www.tiktok.com/tag/freeparkingisnt?refer=embed" target="_blank" title="freeparkingisnt"&gt;#freeparkingisnt&lt;/a&gt; &lt;a href="https://www.tiktok.com/music/original-sound-7312160889942084394?refer=embed" target="_blank" title="♬ original sound - josh"&gt;♬ original sound - josh&lt;/a&gt; &lt;/section&gt; &lt;/blockquote&gt;

&lt;h2 id="online-comments-about-trader-joes-parking-lots"&gt;Online comments about Trader Joe’s parking lots&lt;/h2&gt;

&lt;p&gt;Here’s what some online commentors have said about the parking lot:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I stopped going to Trader Joe’s weekly because of the parking lot situation. I knew it was only a matter of time that I would forced to murder someone in a Prius over a parking spot.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;I drive past mine somewhat often and only go about once a month when I see it’s emptyish. Tend to buy more and go less frequently&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;I live equal distance from two Trader Joe’s. The parking lots are decent compared to the one in Sherman Oaks. One of the local Trader Joe’s has a security guard who will assist the elderly getting out of the parking spots. That makes me return to that specific location.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;I drive to a TJ further away because that one has more parking. The closer one is in an extremely dense neighborhood, and there is no guarantee you will ever park anywhere close to it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://www.reddit.com/r/traderjoes/comments/1426yd3/trader_joes_explains_why_their_parking_lots_are/"&gt;source, reddit&lt;/a&gt;&lt;/p&gt;

&lt;h2 id="what-managing-a-parking-lot-skillfully-looks-like"&gt;what managing a parking lot skillfully looks like&lt;/h2&gt;

&lt;p&gt;Properly managed, a parking lot will always have about 10% capacity available. This means someone wanting to quickly enter, dash inside, and leave, will be able to do so.&lt;/p&gt;

&lt;p&gt;Ensuring availability means using price/cost to mediate demand.&lt;/p&gt;

&lt;p&gt;Donald Shoup wrote a book &lt;a href="https://www.amazon.com/High-Cost-Parking-Updated-Edition/dp/193236496X/ref=as_li_ss_tl?ie=UTF8&amp;amp;qid=1332084228&amp;amp;sr=8-1&amp;amp;linkCode=ll1&amp;amp;tag=markeurban-20&amp;amp;linkId=65aeac5942c99b794876bb2d2dc32bb0"&gt;The High Cost of Free Parking&lt;/a&gt;, and has a standard recommendation for fixing parking. It would be preferable of american municipal planners properly managed on-street parking, but they don’t, so you, TJ’s, with a bit of my help, have to make up the difference.&lt;/p&gt;

&lt;p&gt;Donald Shoup says:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;charge appropriately for on-street and off-street parking&lt;/li&gt;
  &lt;li&gt;Spend the collected money exactly where it’s being collected&lt;/li&gt;
  &lt;li&gt;Remove parking minimums&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;TJ’s is obviously operating with different constraints than municipal planners, and I appreciate and can accommodate that. There’s plenty of improvement to be had, and TJ’s is in a better position to make this change than nearly anyone, because:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;car-heavy user base&lt;/li&gt;
  &lt;li&gt;very busy stores&lt;/li&gt;
  &lt;li&gt;limited parking&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id="other-reading"&gt;Other Reading&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://www.mcsweeneys.net/articles/i-am-a-trader-joes-parking-lot-and-i-am-here-to-destroy-you"&gt;I Am a Trader Joe’s Parking Lot and I Am Here to Destroy You (A Short Imagined Monologue)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="On Parking in Golden"&gt;https://josh.works/parking-in-golden&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.reddit.com/r/traderjoes/comments/1426yd3/trader_joes_explains_why_their_parking_lots_are/"&gt;Trader Joe’s explains why their parking lots are so small (r/traderjoes)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Josh Thompson</author><pubDate>Wed, 13 Dec 2023 15:00:00 GMT</pubDate><guid isPermaLink="true">https://josh.works/trader-joes-parking</guid></item><item><title>[Hμblog] Libreboot 10-year anniversary</title><link>https://www.devever.net/~hl/u/202312#13a</link><description>&lt;ul&gt;&lt;li&gt;&lt;a href="https://libreboot.org/news/10.html"&gt;Libreboot 10-year anniversary&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Leah Rowe has written an interesting article about the history of the Libreboot project on the project's 10th anniversary. Recommended reading if you're interested in the open source firmware ecosystem.&lt;/p&gt;</description><author>devever.net/~hl</author><pubDate>Wed, 13 Dec 2023 10:11:09 GMT</pubDate><guid isPermaLink="true">https://www.devever.net/~hl/u/202312#13a</guid></item><item><title>Optimizing Ship Load-outs for Highfleet: Problem Setup</title><link>https://jodavaho.io/posts/highfleet-ship-optimization-with-scip.html</link><description>&lt;h2 id="over-engineering-highfleet-load-outs-using-integer-programming"&gt;Over-engineering Highfleet load-outs using Integer Programming&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;I built a highfleet ship optimizer by bundling a fast C optimizer, binding to
a higher level language, and providing a web API. Then,
&lt;a href="https://github.com/altho"&gt;Altho&lt;/a&gt; built a front-end. Try it at
&lt;a href="https://hfopt.jodavaho.io/optimizer"&gt;hfopt.jodavaho.io/optimizer&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This post is about setting up the problem. See &lt;a href="https://jodavaho.io/tags/highfleet.html"&gt;related posts&lt;/a&gt;.
&lt;figure&gt;&lt;img alt="Dieselpunk warship schematic. Made with Midjourney." src="https://jodavaho.io/img/brownprint.png" /&gt;&lt;figcaption&gt;
			&lt;p&gt;Dieselpunk warship schematic. Made with Midjourney.&lt;/p&gt;
		&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/HighFleet" title="Wikipedia: Highfleet"&gt;Highfleet&lt;/a&gt; is an incredibly fun game. You take control of a small set of
ships, raid and pillage and revolt your way to glory. Along the way you
must carefully manage your resources to keep your ships repaired, and customize
them to the situation at hand. Lovely time.&lt;/p&gt;
&lt;p&gt;The most addicting part, as is common in this genre&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;, is the ship design and
customization. You can fully tailor your fleet to your desired play style!
Instead of rave about the gameplay, let&amp;rsquo;s talk about how you design ships.&lt;/p&gt;
&lt;h3 id="ship-design-problem-statement"&gt;Ship Design Problem statement&lt;/h3&gt;
&lt;p&gt;In Highfleet, you can &amp;ldquo;pre-design&amp;rdquo; ships before starting a campaign. You have a
limited starting budget, so you need to balance power, weight, engines, guns,
ammo types, layout, crew capacity, sensing, etc, to get the desired range,
speed, firepower, survivability, and combat time, while minimizing cost. This
problem is masterfully posed by the developers, and has hooked a small
community for years.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m interested in automating this, to a certain extent. Can we at &lt;em&gt;least&lt;/em&gt; come
up with an optimal module set - and leave the layout / cosmetics to the
creative user? Staring at the above paragraph, one might see this is a classic
optimization problem&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;. Words like &amp;ldquo;minimize&amp;rdquo;, &amp;ldquo;select&amp;rdquo;, &amp;ldquo;all while &amp;hellip;&amp;rdquo;,
&amp;ldquo;such that&amp;rdquo;, etc are clues.&lt;/p&gt;
&lt;p&gt;We can re-state above as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Select&lt;/em&gt; zero or more whole modules from a list&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Such that&lt;/em&gt; the modules have all their requirements satisfied (e.g., engines have fuel, guns have ammo)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;all while&lt;/em&gt; &lt;strong&gt;minimizing&lt;/strong&gt; cost&lt;/li&gt;
&lt;li&gt;and &lt;em&gt;satisfying&lt;/em&gt; some desired final ship stats&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Easy. Formulating the problem is half the battle.&lt;/p&gt;
&lt;h3 id="solving"&gt;Solving&lt;/h3&gt;
&lt;p&gt;To solve a problem like this you need the defining equations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &amp;ldquo;cost&amp;rdquo; function. In this case, it&amp;rsquo;s literally the dollar value of each module - easily obtained by writing down the values from the game. This is also called the &amp;ldquo;Objective Function&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;The &amp;ldquo;feasibility constraints&amp;rdquo;, which essentially answer: Is this a valid ship at all? Without these, we could choose modules that &lt;em&gt;seem&lt;/em&gt; to satisfy constraints, but would never fly in-game. For example: &amp;ldquo;Do we have enough ammo feeds to keep the guns working&amp;rdquo;, this again we can copy down from in-game stats&lt;/li&gt;
&lt;li&gt;The &amp;ldquo;&lt;a href="https://en.wikipedia.org/wiki/Tradespace" title="Wikipedia: Trade Space"&gt;trade space&lt;/a&gt; equations&amp;rdquo;. In this case, we want to know the following: How does the selection of modules affect the desired final ship stats like Range, Speed, Firepower, etc. This turns out to be very hard, but doable with help.&lt;/li&gt;
&lt;li&gt;We also want the &amp;ldquo;desired stats&amp;rdquo;, which are just &amp;ldquo;constraints&amp;rdquo; we impose on the trade space. We&amp;rsquo;ll get this from the users as a set of valid &lt;em&gt;ranges&lt;/em&gt; (e.g., speed is greater than X, weight is less than Y, blah blah)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Great - all we need to do is reverse-engineer how the in-game stats are calculated. &lt;em&gt;easy right&lt;/em&gt;? Instead, I reached out to the game developer, and &lt;a href="https://reddit.com/r/highfleet"&gt;r/highfleet&lt;/a&gt;, to see what they could share. Everyone had some models, and Koshutin didn&amp;rsquo;t reply. So, I had to do it myself for the most part.&lt;/p&gt;
&lt;h3 id="reverse-engineering-the-game-stats"&gt;Reverse Engineering the Game Stats&lt;/h3&gt;
&lt;p&gt;For completeness, here is the list of relevant stats, obtained by parsing the ship files (which deserves its own post).&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;aavalue, airspeed, ammobox_need, ammobox_total, at_left, at_right, b.x, b.y, combatvalue, crew_capacity, crew_need, dynamic, dynamic_landing, dynamic_map, dynamic_map_repaired, firepower_guns, fss_capacity, fss_total, fuel, fuel_capacity, fuel_need, fuel_need_cr, fuel_on, hp, hp_integral, hp_max, mass, parts, parts_integral, power, power_need, power_real, power_total, power_total_repaired, price, r, signature, signature_ir, signature_rd, thrust_left, thrust_left_landing, thrust_left_map, thrust_left_map_repaired, thrust_right, thrust_right_landing, thrust_right_map, thrust_right_map_repaired, weapons_on
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Some we can throw right out. &lt;code&gt;b.x, b.y&lt;/code&gt; for example make &lt;em&gt;no sense&lt;/em&gt; to me. Some (&lt;code&gt;weapons_on&lt;/code&gt;) probably have more to do with the campaign mode than the performance. Some (&lt;code&gt;airspeed, r&lt;/code&gt;) are &lt;em&gt;exactly&lt;/em&gt; what we want to reverse engineer and optimize for.&lt;/p&gt;
&lt;p&gt;My explorations were fairly crude. I&amp;rsquo;d download many of the fan-made ships from reddit or discord, check the stats, make a spreadsheet, and start plotting to see if the relationships were obvious. Some guesses were obvious (&lt;code&gt;airspeed&lt;/code&gt; should be a function of &lt;code&gt;thrust/weight&lt;/code&gt;, which it was). Some were not (range (&lt;code&gt;r&lt;/code&gt;?), in particular).&lt;/p&gt;
&lt;p&gt;For forming the equations, some &lt;a href="https://en.wikipedia.org/wiki/Dimensional_analysis" title="Wikipedia: Dimensional Analysis"&gt;unit analysis&lt;/a&gt; really paid off. For example, range ultimately (sensibly) was essentially:&lt;/p&gt;
$$ r \approx C_1 \cdot \frac{ \text{fuel_capacity} }{ \text{fuel_consumption} } \cdot \text{airspeed} + C_2 $$&lt;p&gt; which has units &lt;code&gt;meters&lt;/code&gt;- note the unknown constants though&lt;sup id="fnref:3"&gt;&lt;a class="footnote-ref" href="#fn:3"&gt;3&lt;/a&gt;&lt;/sup&gt;. Determining the constants is a job for &lt;a href="https://en.wikipedia.org/wiki/Linear_regression" title="Wikipedia: Linear Regression"&gt;linear regression&lt;/a&gt;! I won&amp;rsquo;t plot out the details, you can see them &lt;a href="https://github.com/jodavaho/hf-study/blob/master/Regression.ipynb" title="Github: Jodavaho/HF-Study"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;At &lt;a href="https://github.com/jodavaho/hf-study/blob/master/Regression.ipynb" title="Github: Jodavaho/HF-Study"&gt;this repo&lt;/a&gt; you can see how it worked. Note &lt;a href="https://github.com/jodavaho/hf-study/blob/master/ships-data.csv" title="Github: Jodavaho/HF-Study/csv"&gt;this file&lt;/a&gt; with ship data, hand crafted! It has a sparses set of stats manually pulled from ships. Just enough to confirm the relationships. Once I had those I could pull up any ship from reddit or discord, and verify it. It was always close enough. Of course, I have to re-do this every time the game balances, but soon, I&amp;rsquo;ll automate this - another post to write.&lt;/p&gt;
&lt;figure&gt;&lt;img alt="from study notebook, showing combat time regression" src="https://jodavaho.io/img/regression-study-1.png" /&gt;&lt;figcaption&gt;
			&lt;p&gt;from study notebook, showing combat time regression&lt;/p&gt;
		&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I did discover some hidden features this way, did you know generators require fuel? Makes sense, but at the time this was undocumented.&lt;/p&gt;
&lt;p&gt;So, that&amp;rsquo;s how I was able to pin down the relationshps for the &amp;ldquo;desired stats&amp;rdquo; from the game files. Next, we can formulate the optimizer.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Mr. Koshutin, if you are reading this, &lt;em&gt;thank you&lt;/em&gt; for letting the community enjoy your game in this way. You certainly didn&amp;rsquo;t have to!&lt;/em&gt;&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;There is a &lt;a href="https://www.navalgazing.net/Naval-Video-Games" title="Naval Gazing: Naval Video Games"&gt;pseudo-genre&lt;/a&gt; of such games, and highfleet stands alone for its unique setting and fun combat.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;There are some softer constraints, too: Does it look cool? Does it land well? These are subjective and not well posed for optimization, so I ignore those (for now!).&amp;#160;&lt;a class="footnote-backref" href="#fnref:2"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;One might imagine that the constants fall out of the in-game stats, but they do not. I suspect this is because &amp;ldquo;fudge factors&amp;rdquo; are applied.&amp;#160;&lt;a class="footnote-backref" href="#fnref:3"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>jodavaho.io</author><pubDate>Wed, 13 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/highfleet-ship-optimization-with-scip.html</guid></item><item><title>Moral Hazard</title><link>https://www.craigpardey.com/post/2023-12-13-moral-hazard/</link><description>&lt;p&gt;A &lt;a href="https://en.wikipedia.org/wiki/Moral_hazard"&gt;moral hazard&lt;/a&gt; is when an..&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;hellip;actor has an incentive to increase its exposure to risk because it does not bear the full costs of that risk.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It&amp;rsquo;s similar to &lt;a href="https://en.wikipedia.org/wiki/Perverse_incentive"&gt;perverse incentives&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A perverse incentive is an incentive that has an unintended and undesirable result that is contrary to the intentions of its designers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I have spent the last 6 months working closely with a QA team inside a large financial instutition and have become increasingly skeptical of the value they provide - the development team don&amp;rsquo;t test as thoroughly as they should because they know that QA are playing backstop.&lt;/p&gt;</description><author>Craig Pardey</author><pubDate>Wed, 13 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.craigpardey.com/post/2023-12-13-moral-hazard/</guid></item><item><title>Go Libraries/Packages I Like</title><link>https://boyter.org/posts/go-libraries-i-like/</link><description>&lt;p&gt;In no particular order a list of Go libraries/packages I really like and some reasons why.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/rs/zerolog/"&gt;zerolog&lt;/a&gt; I know that the changes to slog make it far closer to being what zero log is now, but I still prefer it for my own use.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/carlmjohnson/requests"&gt;requests&lt;/a&gt; Anyone who works with python for a while learns about the requests library for making HTTP calls. This is similar but for Go. It has a few sharp edges when dealing with websites that don&amp;rsquo;t follow standards, but otherwise is amazing. Coupled with the next library in the list you end up with a pretty solid way to call endpoints.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/philippta/trip"&gt;trip&lt;/a&gt; HTTP client middleware that allows automated retries, with backoff&amp;rsquo;s among other things such as authorization. Coupled with requests it really cleans up your HTTP calls.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/samber/lo"&gt;lo&lt;/a&gt; If you know of the wonderful underscore.js library then you probably have an idea of what this is. Similar idea, providing a lot of helper functions that allow dealing with some of Go&amp;rsquo;s sharp edges more easily. It does abstract away things that you might need to know about if you need absolute performance, but for most things it just makes life easier.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/a-h/templ"&gt;templ&lt;/a&gt; HTML template language for Go. There is nothing you can do in this that you cannot in Go templates, but you get compile time checks of the code rather than actually having to run it. It also means you don&amp;rsquo;t need to worry about embedding template (although this is fairly easy these days anyway). Works really well with HTMX.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tidwall/gjson"&gt;gjson&lt;/a&gt; A simple way to get values out of JSON without needing to unmarshall into a struct. Really useful for tests, as well as annoying API&amp;rsquo;s that have mixed data types.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gorilla/mux"&gt;mux&lt;/a&gt; The inbuilt Go router isn&amp;rsquo;t horrible, but is painful to use. This one supports path variables, and you can specify which http methods it uses.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sqlc-dev/sqlc"&gt;sqlc&lt;/a&gt; Generate type safe Go code to access your database. Of all the DB layer abstractions I have used this is best. Its similar to something I wrote in PHP over a decade ago, although much better.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mvdan/xurls"&gt;xurls&lt;/a&gt; The number of times I have gone looking for a URL regex only to find it misses some edge case&amp;hellip; This solves a lot of that pain, especially when it comes to extracting them out of text.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The below are things I am exploring, but not sold on yet.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Wed, 13 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/go-libraries-i-like/</guid></item><item><title>Using the BlueSky API to change your Handle to your Domain Name</title><link>http://pxtl.ca/2023/12/13/bluesky-api/</link><description>&lt;p&gt;Personally in the post-Muskening twitter world, I'm pulling for
&lt;a href="https://mastodon.social/@pxtl"&gt;Mastodon&lt;/a&gt;, but
&lt;a href="https://bsky.app/profile/pxtl.ca"&gt;Bluesky&lt;/a&gt; is a somewhat interesting platform
too.  One feature of Bluesky that stands out is that you can set your name to be
based on a domain-name you own.  I own Pxtl.ca, so I thought I'd take a crack at
leveraging that feature.&lt;/p&gt;

&lt;p&gt;What follows is a guide on how to do this in Powershell, which is a programming
language you &lt;em&gt;already have installed&lt;/em&gt; if you use Windows.&lt;/p&gt;</description><author>Pxtl.ca</author><pubDate>Wed, 13 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">http://pxtl.ca/2023/12/13/bluesky-api/</guid></item><item><title>Demystifying OpenID Connect's State and Nonce Parameters in ASP.NET Core</title><link>https://nestenius.se/net/demystifying-openid-connects-state-and-nonce-parameters-in-asp-net-core/</link><description>In the world of web application security, OpenID Connect plays a key role in streamlining authentication processes. But what makes it really tick? In this</description><author>Tore Nestenius</author><pubDate>Wed, 13 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://nestenius.se/net/demystifying-openid-connects-state-and-nonce-parameters-in-asp-net-core/</guid></item><item><title>2023 Recap</title><link>https://blog.reiterate.app/misc/2023/12/12/2023-recap/</link><description>I’ve been blogging for three years now. Here’s a look back at what I did for 2023.</description><author>Reiterations</author><pubDate>Tue, 12 Dec 2023 20:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.reiterate.app/misc/2023/12/12/2023-recap/</guid></item><item><title>Getting Started with Tyranids in Warhammer 40k 10th Edition (Dec 2023)</title><link>https://valbaca.com/w40k/2023/12/12/tyranid-starter.html</link><description>Intro</description><author>valbaca’s blog</author><pubDate>Tue, 12 Dec 2023 18:03:38 GMT</pubDate><guid isPermaLink="true">https://valbaca.com/w40k/2023/12/12/tyranid-starter.html</guid></item><item><title>Genestealer Cults Rules FAQ (May 2024)</title><link>https://valbaca.com/w40k/2023/12/12/gsc-rules-faq.html</link><description>Intro This post goes over the Frequently Asked Questions about Genestealer Cults Rules. I plan to write one about the faction itself later; as in, what models are good etc.</description><author>valbaca’s blog</author><pubDate>Tue, 12 Dec 2023 18:03:38 GMT</pubDate><guid isPermaLink="true">https://valbaca.com/w40k/2023/12/12/gsc-rules-faq.html</guid></item><item><title>How I learnt about not compromising</title><link>https://ilearnt.com/blog/thebalance/</link><description>&lt;p&gt;I have just finished re-reading the book &lt;a href="[Title]%28https://www.amazon.co.uk/dp/0385516517%29"&gt;The Spark&lt;/a&gt;, a fictional account of a sports agent and his journey to learn about Cirque de Soleil from the inside.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Tue, 12 Dec 2023 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/thebalance/</guid></item><item><title>What I Like About Nix</title><link>https://davi.sh/blog/2023/12/what-i-like-about-nix/</link><description>&lt;p&gt;I got a new computer recently and decided to take the plunge setting it up with Nix&lt;sup&gt;&lt;a href="#user-content-fn-1" id="user-content-fnref-1"&gt;1&lt;/a&gt;&lt;/sup&gt;. &lt;a href="https://davi.sh/til/nix/nix-macos-setup/"&gt;I wrote up a snippet&lt;/a&gt; on how I set everything up and you can find my whole configuration &lt;a href="https://github.com/davish/setup"&gt;on GitHub&lt;/a&gt;. I’ve only scratched the surface of what Nix can do – In this post I wanted to focus on my subjective impression of Nix so far and why I feel I’ve been enjoying it.&lt;/p&gt;
&lt;h2 id="long-term-shared-memory"&gt;Long-term, shared memory&lt;/h2&gt;
&lt;p&gt;No need to worry about remembering what switch you flipped in settings six months ago after reading an OSXDaily.com post, or what command you ran in a shell at 3am the morning you unboxed your new computer. Any change you’ve made to, for example, &lt;a href="https://github.com/davish/setup/blob/main/darwin/default.nix#L46"&gt;hide the dock by default&lt;/a&gt; or allow for repeating keys in &lt;a href="https://github.com/davish/setup/blob/main/darwin/default.nix#L48-L49"&gt;VSCode and other editors&lt;/a&gt; is there for later review.&lt;/p&gt;
&lt;p&gt;On macOS these system preferences are normally either configured in the point-and-click System Preferences interface or specified in loosely documented &lt;code&gt;defaults write&lt;/code&gt; invocations that you can find all over the internet. &lt;a href="https://github.com/LnL7/nix-darwin"&gt;&lt;code&gt;nix-darwin&lt;/code&gt;&lt;/a&gt; is a Nix tool I’m using that lets you configure virtually all of these settings from a Nix file.&lt;/p&gt;
&lt;p&gt;Making your own configuration declarative is good enough, but on top of that people have built up modules and abstractions around more complex configuration like &lt;a href="https://github.com/LnL7/nix-darwin/blob/4b9b83d5a92e8c1fbfd8eb27eda375908c11ec4d/modules/security/pam.nix"&gt;enabling &lt;code&gt;sudo&lt;/code&gt; to use TouchID&lt;/a&gt; or &lt;a href="https://github.com/LnL7/nix-darwin/tree/4b9b83d5a92e8c1fbfd8eb27eda375908c11ec4d/modules/services"&gt;running daemons through launchd&lt;/a&gt; that you can enable with a single line of code.&lt;/p&gt;
&lt;h2 id="a-complete-picture-of-your-environment"&gt;A complete picture of your environment&lt;/h2&gt;
&lt;p&gt;All of my dotfiles, editor themes and extensions, and command-line programs are defined in a single repository. It makes it a lot easier to see how it all interacts with each other and hopefully avoid situations where different dependencies conflict. No one wants to end up here, but it happens all too often, at least to me:&lt;/p&gt;
&lt;p&gt;&lt;img alt="xkcd comic describing a chaotic python environment" src="https://imgs.xkcd.com/comics/python_environment.png" /&gt;&lt;/p&gt;
&lt;h2 id="synchronization-between-machines"&gt;Synchronization between machines&lt;/h2&gt;
&lt;p&gt;I have a desktop and a laptop. They’re now running virtually the same Nix configuration, out of the same repository, with a few tweaks in host-specific overrides. I can work out the kinks in getting Emacs to play nicely with the &lt;a href="https://github.com/koekeishiya/yabai"&gt;Yabai window manager&lt;/a&gt; on my desktop and have it all work on my laptop in a matter of minutes once I pull down the updated configuration repository and reload my Nix environment.&lt;/p&gt;
&lt;p&gt;I should mention these two machines are running different versions of macOS, and virtually everything has worked flawlessly across both.&lt;/p&gt;
&lt;h2 id="reducing-manual-toil"&gt;Reducing manual toil&lt;/h2&gt;
&lt;p&gt;I’ve used &lt;a href="https://karabiner-elements.pqrs.org/"&gt;Karabiner Elements&lt;/a&gt; for years to remap some keys on my keyboard. More recently I’ve moved my configuration over to &lt;a href="https://github.com/yqrashawn/GokuRakuJoudo"&gt;Goku&lt;/a&gt;, a DSL for Karabiner on top of the JSON configuration format. Every time I wanted to change my configuration I would need to remember to run the &lt;code&gt;goku&lt;/code&gt; command. Now that command is run automatically by &lt;a href="https://github.com/nix-community/home-manager"&gt;&lt;code&gt;home-manager&lt;/code&gt;&lt;/a&gt; whenever my &lt;code&gt;karabiner.edn&lt;/code&gt; file changes. When I change my &lt;code&gt;skhd&lt;/code&gt; or &lt;code&gt;yabai&lt;/code&gt; configuration, Nix will automatically restart those services so they pick up their new settings.&lt;/p&gt;
&lt;p&gt;Standardizing the refreshing of all configuration to a single command (&lt;code&gt;darwin-rebuild switch&lt;/code&gt;) and running everything only when it’s needed has been a small change that’s significantly reduced the cognitive load of having a lot of subsystems that may require new configuration, occasional restarts, or other babysitting from time-to-time.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;If the entirety of Nix was a DSL for a package manager and system configuration system I’d be satisfied. But there’s so much more to Nix, from development environments to production artifacts. I’m excited to dive more into those corners of the ecosystem going forward, and hopefully write about it too both here on the blog and over on &lt;a href="https://davi.sh/til/"&gt;the TIL page&lt;/a&gt;!&lt;/p&gt;
&lt;section class="footnotes"&gt;&lt;h2 class="sr-only" id="footnote-label"&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li id="user-content-fn-1"&gt;
&lt;p&gt;Nix isn’t the easiest thing to introduce since it can refer to a few different distinct components or the whole ecosystem. In case you haven’t heard of Nix before or just want to learn more, I find &lt;a href="https://zero-to-nix.com/concepts/nix"&gt;the conceptual documentation on Zero to Nix&lt;/a&gt; to be more approachable than the official docs. &lt;a class="data-footnote-backref" href="#user-content-fnref-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</description><author>Davis Haupt's Blog</author><pubDate>Tue, 12 Dec 2023 14:00:00 GMT</pubDate><guid isPermaLink="true">https://davi.sh/blog/2023/12/what-i-like-about-nix/</guid></item><item><title>📌 My other ramblings on the world wide web</title><link>https://www.zufallsheld.de/2020/10/05/my-other-ramblings/</link><description>&lt;p&gt;Here I list my other writings that I published and talks that I&amp;nbsp;held.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;

&lt;h1 id="writings"&gt;Writings&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.telekom-mms.com/tech-insights/mob-programming-remote-teams-in-the-flow"&gt;Mob Programming - Remote Teams in the Flow | Telekom &lt;span class="caps"&gt;MMS&lt;/span&gt; Blog&lt;/a&gt; (&lt;a href="https://web.archive.org/web/20220610070610/https://devopscon.io/business-company-culture/die-1-prozent-regel-warum-eure-opensource-community-vielleicht-besser-lauft-als-ihr-denkt/"&gt;Internet Archive&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.telekom-mms.com/tech-insights/ansible-best-practices"&gt;Ansible Best-Practices | Telekom &lt;span class="caps"&gt;MMS&lt;/span&gt; Blog&lt;/a&gt; (&lt;a href="https://web.archive.org/web/20201005193236/https://blog.telekom-mms.com/tech-insights/ansible-best-practices"&gt;Internet Archive&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.telekom-mms.com/tech-insights/erfahrungen-und-best-practices-mit-ansible"&gt;Erfahrungen und Best-Practices mit Ansible | Telekom &lt;span class="caps"&gt;MMS&lt;/span&gt; Blog&lt;/a&gt; (&lt;a href="https://web.archive.org/web/20201005193237/https://blog.telekom-mms.com/tech-insights/erfahrungen-und-best-practices-mit-ansible"&gt;Internet Archive&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://entwickler.de/devops/erfahrungen-und-best-practices-mit-ansible"&gt;Updated: Erfahrungen und Best-Practices mit Ansible …&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;</description><author>zufallsheld</author><pubDate>Tue, 12 Dec 2023 13:55:00 GMT</pubDate><guid isPermaLink="true">https://www.zufallsheld.de/2020/10/05/my-other-ramblings/</guid></item><item><title>Visualizing your AAARRP priorities as a way to manage up in your DevRel team</title><link>https://lengrand.fr/aaarrp-metrics-as-a-way-to-manage-expectations-up/</link><description>In this article, I'm describing how we used the AAARRRP framework to visually describe our DevRel team's strategy and clarify the reason for  our day to day activities to our internal stakeholders, and more focus on impact instead.</description><author>Julien's DevRel corner</author><pubDate>Tue, 12 Dec 2023 11:46:10 GMT</pubDate><guid isPermaLink="true">https://lengrand.fr/aaarrp-metrics-as-a-way-to-manage-expectations-up/</guid></item><item><title>Mastodon with docker rootless, compose, and nginx reverse proxy</title><link>https://du.nkel.dev/blog/2023-12-12_mastodon-docker-rootless/</link><description>Everyone &lt;em&gt;talks&lt;/em&gt; about docker rootless, e.g. for security reasons, but no one actually seems to do it. Very little information is available. Migrations from rootfull systems? File permissions? Reverse-proxy setup? Here I go through the steps to run Mastodon</description><author>du.nkel.dev</author><pubDate>Tue, 12 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://du.nkel.dev/blog/2023-12-12_mastodon-docker-rootless/</guid></item><item><title>Capit – AI Social Caption Generator</title><link>https://swiftfox.co/2023/12/capit-ai-social-caption-generator/</link><description>&lt;p&gt;Announcing my latest app &amp;#8211; Capit! The app automatically generates social media captions for Instagram, TikTok, X/Twitter, Reddit, LinkedIn, Pinterest &amp;#38; more. Just pick an image or video, choose your tone, and you will be given five different caption options. I&amp;#8217;ve got a lot of features planned for this app, this is just the beginning!...  &lt;a class="excerpt-read-more" href="https://swiftfox.co/2023/12/capit-ai-social-caption-generator/" title="ReadCapit &amp;#8211; AI Social Caption Generator"&gt;Read more &amp;#187;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://swiftfox.co/2023/12/capit-ai-social-caption-generator/"&gt;Capit – AI Social Caption Generator&lt;/a&gt; first appeared on &lt;a href="https://swiftfox.co"&gt;Swift Fox Software LLC&lt;/a&gt;.&lt;/p&gt;</description><author>Swift Fox Software LLC</author><pubDate>Tue, 12 Dec 2023 01:45:00 GMT</pubDate><guid isPermaLink="true">https://swiftfox.co/2023/12/capit-ai-social-caption-generator/</guid></item><item><title>UK to Curtail Legal Recognition of Trans Immigrants</title><link>https://rya.nc/activism/uk-trans-immigrants.html</link><description>&lt;p&gt;The UK's “Minister for Women and Equality”, Kemi Badenoch, who openly espouses
hate for LGBTQ+ people, has said that many of these countries and territories
no longer meet British standards. This is because they have followed the
overall world-wide trend of improved rights for transgender people, which the
UK opposes. The precise line between political scapegoating and bigotry on
this issue is unclear.&lt;/p&gt;</description><author>rya.nc</author><pubDate>Tue, 12 Dec 2023 00:12:00 GMT</pubDate><guid isPermaLink="true">https://rya.nc/activism/uk-trans-immigrants.html</guid></item><item><title>Building a Clojure CI/CD pipeline of CERTAIN DOOM</title><link>https://blog.janetacarr.com/building-a-clojure-ci-cd-pipeline/</link><description>&lt;h3 id="when-things-go-wrong"&gt;When things go wrong&lt;/h3&gt;&lt;p&gt;Devops is one of those important positions where nobody cares what you&amp;apos;re doing until things stop working. Along with SRE folks, the position may as well be akin to a firefighter. I know this first hand having been a loaded-title myself. You haven&amp;&lt;/p&gt;</description><author>Janet A. Carr</author><pubDate>Mon, 11 Dec 2023 21:55:21 GMT</pubDate><guid isPermaLink="true">https://blog.janetacarr.com/building-a-clojure-ci-cd-pipeline/</guid></item><item><title>Note 123</title><link>https://qubyte.codes/notes/1702314910745</link><description>&lt;p&gt;I’ve been solving #AdventOfCode23 with Swift this year, it’s not been completely smooth sailing, but I really enjoy the language.&lt;/p&gt;</description><author>Qubyte Codes</author><pubDate>Mon, 11 Dec 2023 19:15:10 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/notes/1702314910745</guid></item><item><title>TIL how to create Files and Commits via the Github-API and Github-CLI</title><link>https://www.zufallsheld.de/2023/12/11/til-how-to-create-github-files-via-api/</link><description>&lt;p&gt;Recently, I found myself needing to incorporate a &lt;a href="https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners"&gt;&lt;span class="caps"&gt;CODEOWNERS&lt;/span&gt;&lt;/a&gt;-file to multiple repositories within our Github organization. The objective was to automatically reviewers for pull-requests. And codeowners are a perfect tool for this. This way every colleague in our company can join our Github organization and get write-access to the …&lt;/p&gt;</description><author>zufallsheld</author><pubDate>Mon, 11 Dec 2023 14:55:00 GMT</pubDate><guid isPermaLink="true">https://www.zufallsheld.de/2023/12/11/til-how-to-create-github-files-via-api/</guid></item><item><title>How I learnt to pick ideas that win</title><link>https://ilearnt.com/blog/ideasthatwin/</link><description>&lt;p&gt;I come up with lots of ideas for potential applications, businesses, stories and random other things.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Mon, 11 Dec 2023 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/ideasthatwin/</guid></item><item><title>The simplicity of the modulo operator: how I scaled an inefficient solution on a legacy system</title><link>https://ounapuu.ee/posts/2023/12/11/modulo/</link><description>Sometimes the best solution is the one that works well enough and can be cranked out quickly.</description><author>./techtipsy</author><pubDate>Mon, 11 Dec 2023 06:00:00 GMT</pubDate><guid isPermaLink="true">https://ounapuu.ee/posts/2023/12/11/modulo/</guid></item><item><title>ELI5 Physicists Say That Information Is Never Lost but How the Heck Is It Stored Then and What Theoretical Possibility Could Recover It</title><link>https://studiofreya.org/reddit/eli5-physicists-say-that-information-is-never-lost-but-how-the-heck-is-it-stored-then-and-what-theoretical-possibility-could-recover-it/</link><description>&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://www.reddit.com/r/explainlikeimfive/comments/18g0nry/eli5_physicists_say_that_information_is_never/"&gt;https://www.reddit.com/r/explainlikeimfive/comments/18g0nry/eli5_physicists_say_that_information_is_never/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The difference between a pile of ash and a piece of paper with a word written in ink is information. I get that part.&lt;/p&gt;
&lt;p&gt;What I don&amp;rsquo;t get is why they say it is absolutely critical to physics that information can never truly be lost, thus the problem with black holes potentially destroying information.&lt;/p&gt;
&lt;p&gt;And what I really don&amp;rsquo;t get is HOW that information is stored, and what possible theoretical mechanism could be employed to recover it.&lt;/p&gt;
&lt;p&gt;Say we have a sheet of notebook paper in a glass box. Someone has written a word on that paper with a black Bic ballpoint pen. The box is sealed and the paper is set on fire burning it to a crisp. We then shake the box and the ashes are powdered and scattered.&lt;/p&gt;
&lt;p&gt;Every atom involved is inside the box.&lt;/p&gt;
&lt;p&gt;Where is the information and theoretically how we recover it and find out what the word was?&lt;/p&gt;</description><author>Studiofreya SSG Site</author><pubDate>Mon, 11 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://studiofreya.org/reddit/eli5-physicists-say-that-information-is-never-lost-but-how-the-heck-is-it-stored-then-and-what-theoretical-possibility-could-recover-it/</guid></item><item><title>Applied anthropology</title><link>https://nutcroft.mataroa.blog/blog/applied-anthropology/</link><description>&lt;p&gt;On my way to an anthropology lecture today, I learned much more about humans than I could expect of the lecture. To clarify: the content of the lecture was great yet, with respect to understanding humans, engaging in applied anthropology is inherently unsurpassable.&lt;/p&gt;
&lt;p&gt;While I was cycling through Upper Street in Angel, there was a couple (or maybe three) high-end SUV automobiles, all of whom driving aggresively. The engine revving scared me. I was in the front. The car propelled ahead and predictably and immediately stopped at the red light 30 meters away.&lt;/p&gt;
&lt;p&gt;I’ve always dreamt of facing these people who drive aggressively — and I have a number of times — not with the intention of insulting them — purely out of curiosity and to let them know the effects of their actions. Admittedly, at times I want to swear at them but it wasn’t the case today. I was calm and in a good mood.&lt;/p&gt;
&lt;p&gt;“Can you please not drive so aggresively? It’s scary!”, I told the guy in the front passenger seat with zero bad vibes. “We don’t care”, he said, matter-of-factly, almost satisfied that they don’t. I laughed. “You don’t? Why not?”. “We just don’t… Do you want me to come outside?”. The guy at the back opened the door with the intent of restoring justice with bodily interactions. My bike, however, was in the way and I think he didn’t want to traumatise his friend’s glossy car by forcing his way out. The driver delivered his own statement too: “Oh, fuck off!”. I looked deep into his eyes with the intent of knocking on the door of his soul. He didn’t let me in. I smiled and discretely waved goodbye while cycling away.&lt;/p&gt;
&lt;p&gt;To start with, I was shocked how fast things escalated. I wasn’t angry at all and wouldn’t want to fight; even though, I must confess, it has occured to me multiple times that it would be satisfying to play punches with aggressive car drivers — and cyclists too.&lt;/p&gt;
&lt;p&gt;In retrospect: how should I have responded? Ideally, what should I have done? Practically: what will I do next time there is an aggressive driver behind me revving the car in high traffic central London?&lt;/p&gt;
&lt;p&gt;Do I want to punish them? No, there is no point in punishment. Do I want to change them? Maybe, but first: do I understand them?&lt;/p&gt;
&lt;p&gt;I think it was some kind of celebration. There were many similar cars honking and revving, all of them fancy SUVs, and I think also people talking with each other. It’s ok to make some noise when one is celebrating something, right? I think society can accomondate that and I hope society can accomondate that. I can understand and I have experienced something like one’s team winning at a sport and everybody shouting because of that. An honest and authentic exclamation of excitement.&lt;/p&gt;
&lt;p&gt;Then a random guy on a bike comes up and says “don’t drive aggresively”. Like someone sober at a party at 3 am asking what time everybody is planning to wake up tomorrow.&lt;/p&gt;
&lt;p&gt;Ok — we can accept celebration but what is much more common is that people show off their beloved cars by forcing everyone to pay attention to them. Not to mention scaring everybody else around them, either driving, cycling, or, of course, walking. How do we respond to them? Let’s say the same things happens again: I politely ask for less aggression and someone comes out of the car with intentions of touch.&lt;/p&gt;
&lt;p&gt;One option would be to accept them as they are. Maybe it’s ok for people to honk and rev their engines once a while — what’s the harm? But it's kind of an egotistical act at everybody else’s expense.&lt;/p&gt;
&lt;p&gt;Another option would be to accept that one cannot do anything the moment it happens. The solution is a process that takes place other times. Maybe that’s true and maybe I can conclude to this. But something doesn’t feel right with this conclusion either. There is something missing: the fact that the feeling I’m left with after an engine revving is the feeling of being silenced.&lt;/p&gt;
&lt;p&gt;Here’s where I conclude. Next time I encounter a revver, I will come up to their car and politely ask that they stop driving aggressively. Then, they say “fuck off” and proceed to exit the car to fight. At that point I avoid fighting by running around while asking them to consider their actions and reflect on the fact of whether their actions are justified. Does a simple ask warrant punching me? I didn’t even throw an egg to their car, which I have sometimes considered.&lt;/p&gt;
&lt;p&gt;However much both the driver and myself would love to fight, this would be a loss for everybody in society except for our ego. It is exactly for this reason that I want to resist punching them. It is exactly this kind of urge one must fight off to achieve democracy. This, along with answering the question: how can we help this person feel they were listened to, that they matter, and that they are loved without the need to prove physical overpowering?&lt;/p&gt;
&lt;p&gt;I’m sure one day we’ll figure it out.&lt;/p&gt;</description><author>nutcroft</author><pubDate>Mon, 11 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://nutcroft.mataroa.blog/blog/applied-anthropology/</guid></item><item><title>A Picture Is Worth a Thousand Words</title><link>https://backendhance.com/blog/2023/a-picture-is-worth-a-thousand-words/</link><description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;Ein Bild sagt mehr als tausend Worte&amp;rdquo;&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Dieses Sprichwort habe ich schon früh gehört. Vermutlich das erste Mal in der Schulzeit. Es ist eine Weisheit, die wir schon lange weiter tragen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Vielleicht verbindest du dieses Sprichwort eher mit Bereichen wie Marketing, Statistik, Politik&lt;/strong&gt; oder du denkst wie ich an die Weisheiten der Lehrer von damals.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Etwas, das ich in den letzten Jahren immer wieder realisiert habe, ist: Es ist auch für uns in der Softwareentwicklung relevant.&lt;/strong&gt;&lt;/p&gt;</description><author>Backendhance</author><pubDate>Mon, 11 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/a-picture-is-worth-a-thousand-words/</guid></item><item><title>Insights and questions from the original waterfall paper</title><link>https://ntietz.com/blog/insights-from-the-original-waterfall-paper/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;The &lt;a href="https://en.wikipedia.org/wiki/Waterfall_model"&gt;waterfall model&lt;/a&gt; is probably the most reviled methodology in software engineering.
This methodology was first described in a &lt;a href="https://dl.acm.org/doi/10.5555/41765.41801"&gt;1970 paper&lt;/a&gt; by Dr. Winston Royce.
This paper didn't call it waterfall, nor did it &lt;em&gt;endorse&lt;/em&gt; the technique, and the paper contains a lot of good insights and raises some interesting questions.
Let's take a look at some of those.&lt;/p&gt;
&lt;h1 id="essential-steps-of-software-development"&gt;Essential steps of software development&lt;/h1&gt;
&lt;p&gt;Royce says there are two essential steps in all programming: analysis and coding.
It's not defined what goes into analysis, but I think we can safely assume it includes thinking about the problem and how to solve it.
I think it's pretty clear that these steps are always involved.
For extremely small programs, maybe they're all you need, though you probably don't do them serially.&lt;/p&gt;
&lt;p&gt;The other steps involved for larger programs are requirements, program design, testing, and operations.&lt;/p&gt;
&lt;p&gt;One interesting thing here is that I think these are all done at &lt;em&gt;all&lt;/em&gt; sizes of software, they're just not done explicitly and separately.
Let's say you write a small program, like a solution to an Advent of Code problem.
For this, you need to get the requirements from the problem description, do some analysis on it, design your program, write it up, test it, and then run it for the answer.
But these flow together, and code/test/operate get lumped together, and requirements/analysis/design get lumped together—with both of &lt;em&gt;those&lt;/em&gt; groups getting intermingled as well.
They're not done serially one after another, but each is done at some point.&lt;/p&gt;
&lt;p&gt;What are really the essential steps of software development?
I'm not sure.
I think the breakdown of activities he mentions in this paper is interesting and a nice way to think about the activities we engage in, and I can't really go further than that at the moment.&lt;/p&gt;
&lt;h1 id="what-is-the-role-of-management"&gt;What is the role of management?&lt;/h1&gt;
&lt;p&gt;One point that Royce makes is... interesting:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The prime function of management is to sell these concepts [of testing, documentation, analysis, etc.] to both groups [developers and customers] and then enforce compliance on the part of development personnel.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;He continues this later on, too:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The first rule of managing software development is ruthless enforcement of documentation requirements.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And this quote is followed by saying that if documentation isn't good enough, then &lt;em&gt;replace management&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;So, this makes clear Royce's view on management's role: strictly enforcing rules and proper development practice.
If they don't &lt;em&gt;ruthlessly&lt;/em&gt; enforce documentation processes, then they'll be fired.
And they need to make sure developers do their testing and analysis and design, too.&lt;/p&gt;
&lt;p&gt;I mean... I don't know what it was like in the 70s, a couple of decades before my time.
So this could be the right take at the time.
In the present day, it seems &lt;em&gt;very&lt;/em&gt; antithetical to what I've experienced in the teams I work on.&lt;/p&gt;
&lt;p&gt;On the teams I'm on, what I've generally seen is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Engineers advocate for testing, for requirements, for explicit design time&lt;/li&gt;
&lt;li&gt;Management pushes for &lt;em&gt;less&lt;/em&gt; of these in some instances&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the opposite of what he says happens!
I'll give him this, though: developers sure &lt;em&gt;do&lt;/em&gt; like skipping documentation, and customers &lt;em&gt;do&lt;/em&gt; want to avoid paying for these things.&lt;/p&gt;
&lt;p&gt;For me, the role of management is not as ruthless enforcer but as facilitator.
Software engineering is more mature as a field than it was 53 years ago, and we have some established best practices.
As practitioners, we take pride in our work and we do push for testing, analysis, all the good stuff.
And the role of management is to make sure that everything hangs in balance between technical depth and business needs, and to make sure that the existing processes facilitate that balance.&lt;/p&gt;
&lt;p&gt;But if things aren't happening, you don't step in as a ruthless enforcer
You look and figure out why, and work with the team &lt;em&gt;together&lt;/em&gt; to shift processes to make those things happen.&lt;/p&gt;
&lt;h1 id="what-are-we-optimizing-for"&gt;What are we optimizing for?&lt;/h1&gt;
&lt;p&gt;He states early on that "[Separate stages of development] must be planned and staffed differently for best utilization of program resources."
This describes a world where we have dedicated staff for gathering requirements, different staff for designing the program, yet more staff to write it, another team to test it, and some poor soul has to put our mess into production.&lt;/p&gt;
&lt;p&gt;In contrast, most teams today take a much more multidisciplinary approach.
Some go to the extreme, and everyone does everything.
Most are somewhere in the middle: dedicated testing staff are present, but everyone does some testing; product managers are responsible for requirements, but everyone helps; architects do a lot of design, but each engineer does some architecture.&lt;/p&gt;
&lt;p&gt;The key thing though is that last part: &lt;em&gt;"for best utilization of program resources"&lt;/em&gt;.
Here, "program" refers to the project and its staffing, not to the software.
And that's the thing, he's optimizing for best utilizing each individual's time and saving money on personnel.&lt;/p&gt;
&lt;p&gt;In contrast, modern software development prioritizes other things over direct resource utilization.
Time to market, quick validation, all the things to make sure we're going in the right direction.
We slow down a little and waste a little bit of each person's time, but we have a lot less backtracking to do.&lt;/p&gt;
&lt;p&gt;I could see separate roles making sense in a situation where you do have much clearer requirements.
Does something like that exist?
Good question.
But if it does, separate roles might make sense (I'm not fully convinced, but maybe).
For everything else, prioritizing figuring out what we're &lt;em&gt;doing&lt;/em&gt; makes more sense than optimizing for full utilization of time.&lt;/p&gt;
&lt;h1 id="write-it-twice"&gt;Write it twice!&lt;/h1&gt;
&lt;p&gt;One of the best pieces of advice in this paper (not that I'm biased, having &lt;a href="https://ntietz.com/blog/throw-away-your-first-draft/"&gt;written something similar&lt;/a&gt;) is to write it &lt;em&gt;twice&lt;/em&gt;.
The first version should be a fast version to learn what we're doing and gain real-world insight.
Then the second version is the final draft that goes to the customer and should meet requirements.&lt;/p&gt;
&lt;p&gt;This is great, because it highlights what we run into all the time: we don't know if our solutions work until we try them.
We don't really know &lt;em&gt;what the problem is&lt;/em&gt; until we try to solve it.
Having multiple iterations is a fantastic way to try things, learn those hard lessons, and still have time in the schedule to fix everything.&lt;/p&gt;
&lt;p&gt;The recommendation to do it twice, in full, I think is interesting and is something to aspire to.
It's easier to advocate for small iterations and small throw-away prototypes, and those are super valuable.
If you take nothing else away from this paper, go try doing a throw-away version first.&lt;/p&gt;
&lt;h1 id="the-gendered-language"&gt;The gendered language&lt;/h1&gt;
&lt;p&gt;It's always a little bit of a shock going back and reading a paper from the 70s.
Every single pronoun is "he" or "his," and that just really grates&lt;sup class="footnote-reference" id="fr-1-1"&gt;&lt;a href="https://ntietz.com/blog/insights-from-the-original-waterfall-paper/#fn-1"&gt;[1]&lt;/a&gt;&lt;/sup&gt;.
Not every person on a tech project is a boy, you know.&lt;/p&gt;
&lt;p&gt;The norm in tech has pretty well shifted as far as I see.
There's still &lt;em&gt;plenty&lt;/em&gt; of sexism to go around, but there's less blatant gendered language at least.
We've got a long way to go, and sometimes reading papers from the past is nice to remind us of how the norms &lt;em&gt;have&lt;/em&gt; changed.&lt;/p&gt;
&lt;p&gt;Let's let this remind us that change is possible.
Collectively, we've shifted away from default he/him pronouns for anonymous people.
We can continue to make a more equitable world.
It's going to take a while, it's going to hurt, and it's worth fighting for it ❤️.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;As a historical artifact, Royce's paper gives a lot of interesting insights.
It's cool to see some of the things he discusses in here be fully realized, and to see other ways in which our field has transcended where it was in the 70s.&lt;/p&gt;
&lt;hr /&gt;
&lt;hr /&gt;&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-1"&gt;
&lt;p&gt;The USCF's Official Rules of Chess appear to have previously been this way, too.
They've made an effort to update them resulting in some inconsistencies, including at least one time where they switched gender of pronouns midsentence while referring to the same antecedent. &lt;a href="https://ntietz.com/blog/insights-from-the-original-waterfall-paper/#fr-1-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 11 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/insights-from-the-original-waterfall-paper/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Scientists find exciting new way to talk about nothing in press releases</title><link>https://taylor.town/exciting-new-way</link><description>In a groundbreaking development, a team of renowned scientists unlocked a revolutionary method to discuss absolutely nothing in press releases.</description><author>taylor.town</author><pubDate>Mon, 11 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/exciting-new-way</guid></item><item><title>Santa Prefers Wealthy Sick Children</title><link>https://taylor.town/santa-prefers</link><description>This is the first study, to our knowledge, to dispel the myth that Santa visits children based on behaviour and suggests socioeconomic deprivation plays a greater role in determining a visit.</description><author>taylor.town</author><pubDate>Mon, 11 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/santa-prefers</guid></item><item><title>TIL: macOS’ Hidden Gem – The "caffeinate" Command!</title><link>https://james.brooks.page/blog/macos-caffeinate-command</link><description>Did you know macOS has a built-in alternative to Caffeine? It’s called caffeinate and it’s awesome.</description><author>James Brooks</author><pubDate>Mon, 11 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://james.brooks.page/blog/macos-caffeinate-command</guid></item><item><title>Handling Process Debt in IT</title><link>https://vadimkravcenko.com/shorts/process-debt/</link><description>&lt;p&gt;Question:&lt;/p&gt;
&lt;/p&gt;
&lt;p&gt; Answer:&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://vadimkravcenko.com/shorts/process-debt/"&gt;Handling Process Debt in IT&lt;/a&gt; appeared first on &lt;a href="https://vadimkravcenko.com"&gt;Vadim Kravcenko&lt;/a&gt;.&lt;/p&gt;</description><author>Vadim Kravcenko</author><pubDate>Sun, 10 Dec 2023 18:25:34 GMT</pubDate><guid isPermaLink="true">https://vadimkravcenko.com/shorts/process-debt/</guid></item><item><title>The Winterkeeper</title><link>https://nicolaiarocci.com/the-winterkeeper/</link><description>&lt;p&gt;The Guardian&amp;rsquo;s &lt;em&gt;The Winterkeeper: A Lifetime Spent Protecting Yellowstone National Park&lt;/em&gt; is a beautiful short documentary I truly enjoyed watching.&lt;/p&gt;
&lt;p&gt;&lt;div style="padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
      
    &lt;/div&gt;

&lt;!-- raw HTML omitted --&gt;
A little research on Steven Fuller, the protagonist, allowed me to dig out some promising &lt;a href="https://mountainjournal.org/a-winterkeeper-remembers-his-piece-in-natgo"&gt;reading material&lt;/a&gt;.&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Sun, 10 Dec 2023 08:05:25 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/the-winterkeeper/</guid></item><item><title>The Restaurant at the End of the Universe</title><link>https://vit.baisa.cz/books/the-restaurant-at-the-end-of-the-universe/</link><description>second book in the Hitchhiker&amp;rsquo;s Guide to the Galaxy series</description><author>Vít Baisa</author><pubDate>Sun, 10 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://vit.baisa.cz/books/the-restaurant-at-the-end-of-the-universe/</guid></item><item><title>TUIs and mouseless computing</title><link>https://vit.baisa.cz/notes/work/inject/tui-mouseless/</link><description>notes on a presentation about TUIs and mouseless computing for INJECT</description><author>Vít Baisa</author><pubDate>Sun, 10 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://vit.baisa.cz/notes/work/inject/tui-mouseless/</guid></item><item><title>Setting Up macOS for a Developer</title><link>https://sal.dev/macos/setting-up-macbook-pro/</link><description>&lt;p&gt;In this post, I’ll share the key tools and configurations I use when setting up a new macOS environment for software engineering.&lt;/p&gt;

&lt;h1 id="applications"&gt;Applications&lt;/h1&gt;

&lt;p&gt;I’m a fan of Firefox and a bunch of other tools, but I’ll keep this to developer-related tools.&lt;/p&gt;

&lt;h3 id="iterm2"&gt;&lt;a href="https://iterm2.com/"&gt;iTerm2&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;I believe it is better than the built-in Terminal. Probably.&lt;/p&gt;

&lt;p&gt;Configure the following shortcuts from &lt;a href="https://jonnyhaynes.medium.com/jump-forwards-backwards-and-delete-a-word-in-iterm2-on-mac-os-43821511f0a"&gt;this tutorial&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;⌥ ⌫&lt;/code&gt; for deleting a word&lt;/li&gt;
  &lt;li&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;⌥ ←&lt;/code&gt; for going back a word&lt;/li&gt;
  &lt;li&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;⌥ →&lt;/code&gt; for going forward a word&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Set the Login command in profile to open &lt;code class="language-plaintext highlighter-rouge"&gt;tmux&lt;/code&gt; or reuse an existing session. When I accidentally close the window, this keeps me from terminating a long-running program.&lt;/p&gt;

&lt;figure class="highlight"&gt;&lt;pre&gt;&lt;code class="language-bash"&gt;tmux attach &lt;span class="nt"&gt;-t&lt;/span&gt; base &lt;span class="o"&gt;||&lt;/span&gt; tmux new &lt;span class="nt"&gt;-s&lt;/span&gt; base&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id="1password"&gt;&lt;a href="https://1password.com/"&gt;1Password&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;Managing passwords and keys is 100x easier with 1Password and they’re more secure than a lot of the other options out there. Their &lt;a href="https://developer.1password.com/docs/ssh/manage-keys/"&gt;SSH key management&lt;/a&gt; and &lt;a href="https://developer.1password.com/docs/ssh/"&gt;git signing&lt;/a&gt; support is a must-use.&lt;/p&gt;

&lt;h1 id="folders"&gt;Folders&lt;/h1&gt;

&lt;h3 id="developer"&gt;~/Developer&lt;/h3&gt;

&lt;p&gt;I used to call the folder &lt;code class="language-plaintext highlighter-rouge"&gt;~/dev&lt;/code&gt; or &lt;code class="language-plaintext highlighter-rouge"&gt;~/Development&lt;/code&gt;, but if you call it “Developer”, then Apple will reward you with a special folder icon.&lt;/p&gt;

&lt;p&gt;&lt;img alt="Pretty cute Developer folder" src="/assets/article_images/2023-12-10-setting-up-macbook-pro/Developer-folder.jpg" /&gt;&lt;/p&gt;

&lt;h3 id="screenshots"&gt;~/Screenshots&lt;/h3&gt;

&lt;p&gt;If you also take a lot of screenshots, you’ll want to see &lt;a href="/macos/macos-screenshotting-tips-and-tricks/"&gt;this post&lt;/a&gt; about setting up a screenshot folder.&lt;/p&gt;

&lt;h1 id="developer-tools"&gt;Developer Tools&lt;/h1&gt;

&lt;p&gt;macOS changed the default from &lt;code class="language-plaintext highlighter-rouge"&gt;bash&lt;/code&gt; to &lt;code class="language-plaintext highlighter-rouge"&gt;zsh&lt;/code&gt; &lt;a href="https://www.theverge.com/2019/6/4/18651872/apple-macos-catalina-zsh-bash-shell-replacement-features"&gt;in 2019&lt;/a&gt; so if your previous computer was an Intel-based MacBook you’re in for a very-similar experience.&lt;/p&gt;

&lt;h3 id="oh-my-zsh"&gt;&lt;a href="https://github.com/ohmyzsh/ohmyzsh"&gt;Oh My Zsh&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;It is a framework for managing shell plugins.&lt;/p&gt;

&lt;figure class="highlight"&gt;&lt;pre&gt;&lt;code class="language-bash"&gt;&lt;span class="c"&gt;# .zshrc, line ~73&lt;/span&gt;
&lt;span class="nv"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;
  asdf
  brew
  git
  z
&lt;span class="o"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id="asdf"&gt;&lt;a href="https://asdf-vm.com/"&gt;asdf&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The one-stop-shop for managing Java/Python/Ruby/whatever versions. If you use Ruby, I recommend enabling the legacy versions so you can use the other version manager &lt;code class="language-plaintext highlighter-rouge"&gt;.ruby-version&lt;/code&gt; files.&lt;/p&gt;

&lt;figure class="highlight"&gt;&lt;pre&gt;&lt;code class="language-bash"&gt;&lt;span class="c"&gt;# $HOME/.asdfrc&lt;/span&gt;
legacy_version_file &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;yes&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id="homebrew"&gt;&lt;a href="https://brew.sh/"&gt;Homebrew&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;It is the most popular way to install command line tools.&lt;/p&gt;

&lt;h3 id="starship"&gt;&lt;a href="https://starship.rs/"&gt;Starship&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;It makes my command prompt look nice.&lt;/p&gt;

&lt;figure class="highlight"&gt;&lt;pre&gt;&lt;code class="language-bash"&gt;sal.dev on  main &lt;span class="o"&gt;[!&lt;/span&gt;?] via 💎 v2.7.8
❯&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id="z"&gt;&lt;a href="https://github.com/rupa/z"&gt;z&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;Command line tool for jumping back in to a project you were working on. For example, if I type &lt;code class="language-plaintext highlighter-rouge"&gt;z ad&lt;/code&gt;, it drops me back into the Advent of Coding project.&lt;/p&gt;

&lt;figure class="highlight"&gt;&lt;pre&gt;&lt;code class="language-bash"&gt;~
❯ z ad

advent-of-code-2023
❯&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id="other"&gt;Other&lt;/h1&gt;

&lt;p&gt;I also like to keep this command handy for clearing out &lt;code class="language-plaintext highlighter-rouge"&gt;git&lt;/code&gt; branches that have been merged to &lt;code class="language-plaintext highlighter-rouge"&gt;main&lt;/code&gt;.&lt;/p&gt;

&lt;figure class="highlight"&gt;&lt;pre&gt;&lt;code class="language-bash"&gt;git branch &lt;span class="nt"&gt;--merged&lt;/span&gt; main | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\*&lt;/span&gt;&lt;span class="s2"&gt; main"&lt;/span&gt; | xargs &lt;span class="nt"&gt;-n&lt;/span&gt; 1 git branch &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id="addendum"&gt;Addendum&lt;/h1&gt;

&lt;p&gt;This post was more for me to remember what I use than for others, but I hope some folks found it useful anyway!&lt;/p&gt;</description><author>Salvatore's Blog</author><pubDate>Sun, 10 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://sal.dev/macos/setting-up-macbook-pro/</guid></item><item><title>Someone interviewed me</title><link>https://ochagavia.nl/blog/someone-interviewed-me/</link><description>A few months back I had an email exchange with Drew Capener, from filtra.io, who is interviewing professionals involved in the Rust community. We ended up having a nice chat, which he transcribed and posted online under the title &amp;ldquo;Contributing to Rust as a novice&amp;rdquo;. It even triggered some discussion on Hacker News!
What did we talk about? The adventure of contributing to Rust in 2014 as a beginner to programming, the crucial role of friendly maintainers, and the challenges involved in working as an independent contractor.</description><author>Consulting on Adolfo Ochagavía</author><pubDate>Sun, 10 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ochagavia.nl/blog/someone-interviewed-me/</guid></item><item><title>"All foods are either protein or candy."</title><link>https://taylor.town/protein-or-candy</link><description>I've found that protein alone is a suitable proxy for tracking all other micronutrients. When I eat enough protein, I'm probably getting enough vitamins, minerals, etc.</description><author>taylor.town</author><pubDate>Sun, 10 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/protein-or-candy</guid></item><item><title>We don't need no stinking bastion host!</title><link>https://boyter.org/posts/who-needs-bastion-hosts/</link><description>&lt;p&gt;So on occasion, I find myself wanting to run SQL against customer databases. Where this gets annoying is that because we are often in AWS using lambda I have no ability to connect to them. Occasionally im able to bully the friendly devops&amp;rsquo; and security people to allow me access to a bastion host, which I can then connect to the SQL database.&lt;/p&gt;
&lt;p&gt;However this is often a painful process if allowed at all, and so with permission I sometimes add a special route into the application allowing me to run arbitrary SQL as needed.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Sun, 10 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/who-needs-bastion-hosts/</guid></item><item><title>Ooty: Friendships, Travel and Painting 📍</title><link>https://trigonaminima.github.io/2023/12/ooty/</link><description>I spent the last two weeks of August in Ooty📍, A hill station in Tamil Nadu. Transitioning from Chennai’s heat to Ooty’s cold within a day was drastic. My hoodie was happy to be out from the bottom of my bag.</description><author>Playground</author><pubDate>Sun, 10 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://trigonaminima.github.io/2023/12/ooty/</guid></item><item><title>Things I've Published and Talks I've Given</title><link>https://jay.jvf.cc/publications/</link><description>&lt;p&gt;I&amp;rsquo;ve been lucky to have the opportunity in my career to speak at conferences
and to have my writing published in many written and digital publications.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re interested in having me speak at your conference, or write for your
publication, please reach out to me.&lt;/p&gt;
&lt;h2 id="conference-talkstrainings"&gt;Conference Talks/Trainings&lt;/h2&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Date&lt;/th&gt;
          &lt;th&gt;Title&lt;/th&gt;
          &lt;th&gt;Conference&lt;/th&gt;
          &lt;th&gt;Video&lt;/th&gt;
          &lt;th&gt;Slides&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;2025-03-06&lt;/td&gt;
          &lt;td&gt;&lt;a href="https://www.socallinuxexpo.org/scale/22x/presentations/openstack-real-world"&gt;OpenStack in the Real World&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;SCALE 22x / OpenInfra Days 2025&lt;/td&gt;
          &lt;td&gt;&lt;a href="https://www.youtube.com/watch?v=W9OmGdtJAAE#t=2h30m"&gt;video&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;&lt;a href="https://docs.google.com/presentation/d/1NdGZf768UOVVbH-_AtX3BVqx3URhj2AI9L4HH4k2SVc/edit#slide=id.p"&gt;slides&lt;/a&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;2024-06-04&lt;/td&gt;
          &lt;td&gt;&lt;a href="https://videos.cern.ch/record/2300350"&gt;Open Infrastructure: From Monumental to Mundane and Back Again &lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;OpenInfra Days 2024, CERN&lt;/td&gt;
          &lt;td&gt;&lt;a href="https://www.youtube.com/watch?v=tGE5T4A-fgc&amp;amp;t=17500s"&gt;video&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;&lt;a href="https://docs.google.com/presentation/d/1klttcmp-7fHBVvWRlc2Ro79QpvD-LgKtRj1U6fFTyg0"&gt;slides&lt;/a&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;2023-11-04&lt;/td&gt;
          &lt;td&gt;&lt;a href="https://osem.seagl.org/conferences/seagl2023/program/proposals/984"&gt;Trust in an Open Source Community&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;SeaGL 2023&lt;/td&gt;
          &lt;td&gt;&lt;a href="https://www.youtube.com/watch?v=tGE5T4A-fgc&amp;amp;t=17500s"&gt;video&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;&lt;a href="https://docs.google.com/presentation/d/1UYrIYX9E1cRlzvQyTetcqJFp9XDvasD3C0BV08FtOHU"&gt;slides&lt;/a&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;2023-03-11&lt;/td&gt;
          &lt;td&gt;&lt;a href="https://www.socallinuxexpo.org/scale/20x/presentations/death-hardware-knowledge"&gt;Death of Hardware Knowledge&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;SCALE 20x&lt;/td&gt;
          &lt;td&gt;&lt;a href="https://www.youtube.com/watch?v=-9SjVINjHUM"&gt;video&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;&lt;a href="https://docs.google.com/presentation/d/1n2dr94zAXD9NuxBtFtnojuiRsEyWVKCoJB6PwXJh-JE"&gt;slides&lt;/a&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;2015-05-20&lt;/td&gt;
          &lt;td&gt;Don&amp;rsquo;t Just Consume; Participate! Operators and the Community&lt;/td&gt;
          &lt;td&gt;OpenStack Summit&lt;/td&gt;
          &lt;td&gt;&lt;a href="https://www.youtube.com/watch?v=mFbpf1dpzNY"&gt;video&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;N/A&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;2014-11-05&lt;/td&gt;
          &lt;td&gt;Hardware in the Cloud; Cleaning up after Bare Metal Tenants&lt;/td&gt;
          &lt;td&gt;OpenStack Summit&lt;/td&gt;
          &lt;td&gt;&lt;a href="https://www.youtube.com/watch?v=2Oi2T2pSGDU"&gt;video&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;N/A&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;2012-11-12&lt;/td&gt;
          &lt;td&gt;&lt;a href="https://www.usenix.org/conference/lisa12/training-program/full-training-program#T7"&gt;How to Secure and Route Your Linux Network&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;USENIX LISA 2012 (Tutorial)&lt;/td&gt;
          &lt;td&gt;N/A&lt;/td&gt;
          &lt;td&gt;N/A&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;2011-12-06&lt;/td&gt;
          &lt;td&gt;&lt;a href="https://www.usenix.org/legacy/event/lisa11/training/tutonefile.html#Tuesday"&gt;How to Secure and Route Your Linux Network&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;USENIX LISA 2011 (Tutorial)&lt;/td&gt;
          &lt;td&gt;N/A&lt;/td&gt;
          &lt;td&gt;N/A&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;2010-11-08&lt;/td&gt;
          &lt;td&gt;&lt;a href="https://www.usenix.org/legacy/event/lisa10/training/tutonefile.html#Monday"&gt;How to Secure and Route Your Linux Network&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;USENIX LISA 2010 (Tutorial)&lt;/td&gt;
          &lt;td&gt;N/A&lt;/td&gt;
          &lt;td&gt;N/A&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="external-blogposts"&gt;External Blogposts&lt;/h2&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Date&lt;/th&gt;
          &lt;th&gt;Title&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;2025-03-26&lt;/td&gt;
          &lt;td&gt;&lt;a href="https://www.gresearch.com/news/invisible-work-of-openstack-eventlet-migration/"&gt;Invisible work of OpenStack: Eventlet Migration&lt;/a&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;2023-06-30&lt;/td&gt;
          &lt;td&gt;&lt;a href="https://www.gresearch.com/blog/article/armada-six-months-in-the-sandbox/"&gt;Armada: Six Months in the Sandbox&lt;/a&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;2023-05-04&lt;/td&gt;
          &lt;td&gt;&lt;a href="https://www.gresearch.com/blog/article/invisible-work-of-openstack-testing-matrices/"&gt;Invisible work of OpenStack: Testing Matrices&lt;/a&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;2023-03-08&lt;/td&gt;
          &lt;td&gt;&lt;a href="https://opensource.com/article/23/3/cloud-hardware"&gt;What Cloud Developers Need to Know About Hardware&lt;/a&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;2022-11-29&lt;/td&gt;
          &lt;td&gt;&lt;a href="https://www.gresearch.com/blog/article/invisible-work-of-openstack-security-bugs/"&gt;Invisible work of OpenStack: Security Bugs&lt;/a&gt;&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="publications-physical"&gt;Publications (physical)&lt;/h2&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Date(s)&lt;/th&gt;
          &lt;th&gt;Title&lt;/th&gt;
          &lt;th&gt;Publication&lt;/th&gt;
          &lt;th&gt;Notes&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;June 2005&lt;/td&gt;
          &lt;td&gt;Linspire Five-O (review)&lt;/td&gt;
          &lt;td&gt;Linux Magazine&lt;/td&gt;
          &lt;td&gt;Physical copy needs to be scanned&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;2000-2003&lt;/td&gt;
          &lt;td&gt;(multiple weekly)&lt;/td&gt;
          &lt;td&gt;Spring Hope Enterprise&lt;/td&gt;
          &lt;td&gt;Worked for years; ending as Sports Editor&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="other-projectsmedia"&gt;Other Projects/Media&lt;/h2&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Date(s)&lt;/th&gt;
          &lt;th&gt;Description&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;May 2024 - Current&lt;/td&gt;
          &lt;td&gt;&lt;a href="https://podcast.gr-oss.io"&gt;GR-OSS OUT Podcast (audio)&lt;/a&gt;, &lt;a href="https://www.youtube.com/playlist?list=PLNWn4-IA6Bx4lokN21n2tLljM1WlqM6JP"&gt;(video)&lt;/a&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Oct 2022 - Mar 2023&lt;/td&gt;
          &lt;td&gt;&lt;a href="https://jay.jvf.cc/officehours/"&gt;Open Source Office Hours&lt;/a&gt;&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;</description><author/><pubDate>Sun, 10 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jay.jvf.cc/publications/</guid></item><item><title>I hate MFA</title><link>https://alexmolas.com/2023/12/10/hate-mfa.html</link><description>I hate MFA, and in this rant post I explain why. It's basically because (1) is distracts me a lot, (2) it forces me to have a smartphone, (3) it's a leaky abstraction, and (4) it can be replaced with better solutions.</description><author>Alex Molas Blog</author><pubDate>Sun, 10 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://alexmolas.com/2023/12/10/hate-mfa.html</guid></item><item><title>A trekker's best friends</title><link>https://smetj.net/a-trekkers-best-friends.html</link><description>&lt;h2&gt;Old friends&lt;/h2&gt;
&lt;p&gt;A little over a year ago, I found myself faced with the inevitable task of
parting ways with my beloved Timberland all-leather hiking shoes. They had
faithfully accompanied me on countless adventures, but time had taken its
toll, and they had begun to show signs of wear and …&lt;/p&gt;</description><author>Jelle Smet</author><pubDate>Sun, 10 Dec 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://smetj.net/a-trekkers-best-friends.html</guid></item><item><title>Introducing CrankWheel</title><link>/introducing-crankwheel/</link><description>&lt;p&gt;
  &lt;img alt="" src="/bimg/7C791470-0409-4A0D-81BC-D11E0B01019F-54078-00024DEEAE0AEE96/crankwheel-blogsize1.jpg" /&gt;
&lt;/p&gt;</description><author>joisig gone awol</author><pubDate>Sat, 09 Dec 2023 23:20:13 GMT</pubDate><guid isPermaLink="true">/introducing-crankwheel/</guid></item><item><title>Analyzing Static Website Logs with AWStats</title><link>https://www.mikekasberg.com/blog/2023/12/09/analyzing-static-website-logs-with-awstats.html</link><description>&lt;p&gt;It’s really common to add Google Analytics to your site to gain valuable
insights about who’s visiting. I think most companies use Google Analytics, and
because it’s free and easy many solo developers and smaller sites do too. But
recently, I think a lot of people have been questioning the value. For many
small or independent blogs and websites, Google Analytics is an
overly-complicated product that’s difficult to use and navigate. Moreover, using
Google Analytics requires loading their relatively large, bloated javascript on
your site. When Google Analytics competitor Plausible was starting out a few
years ago, they highlighted a bunch of &lt;a href="https://plausible.io/blog/remove-google-analytics"&gt;other reasons you might not want to use
Google Analytics&lt;/a&gt;. In short,
there’s gotta be a better analytics tool for small websites right?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This blog&lt;/em&gt; is a small website, and &lt;em&gt;I used to use Google Analytics&lt;/em&gt;. (&lt;strong&gt;I don’t
anymore!&lt;/strong&gt;) I removed Google Analytics from this blog back in 2021. I agree with
most of the criticisms in the article linked above – I don’t want to force my
visitors to be tracked, I don’t want a bloated script potentially slowing down
my site, and Google Analytics is way overkill for what I need. All I really care
about is occasionally checking how many people are visiting my site, seeing
which pages they look at, and watching for errors. Everything else is bonus, as
long as it’s easy to use and understand. For a year or two, I operated without
any analytics at all. I used &lt;a href="https://search.google.com/search-console"&gt;Google Search
Console&lt;/a&gt; to gain a little insight into
which pages people were visiting. Search Console is actually &lt;em&gt;great&lt;/em&gt;, and I’m
still using it today. It provides a simple view of how people find your site
from Google, which is a good approximation for which pages are popular anyway.
But Search Console was never a true representation of all my traffic (because
some people don’t come to my site from Google), and therefore isn’t a perfect
replacement for Google Analytics.&lt;/p&gt;

&lt;p&gt;I wanted to find a better tool for analyzing traffic to my website. I started
looking for alternatives. &lt;a href="https://plausible.io/"&gt;Plausible&lt;/a&gt; seemed really
appealing, and I liked a lot of their selling points. Simple analytics that are
easy to understand. Lightweight script. No cookies. Open source. Unfortunately,
I didn’t like their price tag. As of December 2023, their &lt;a href="https://plausible.io/#pricing"&gt;cheapest
plan&lt;/a&gt; is $9/mo. While that’s probably reasonable
for most customers, it just doesn’t make sense for me. This blog is a statically
generated site that I operate for &lt;em&gt;dollars&lt;/em&gt; per year. And while I &lt;em&gt;could&lt;/em&gt; afford
to pay $9/mo, I just don’t want to. I don’t think I’d get $108 dollars of annual
value from analytics on my personal blog that doesn’t make any money. On top of
that, I’ve come to enjoy operating this blog &lt;em&gt;really frugally&lt;/em&gt;. I take some pride
in keeping things simple and cheap, and in knowing that you &lt;em&gt;can&lt;/em&gt; run a website
really well on a very low budget using open source software.&lt;/p&gt;

&lt;p&gt;So, if not Plausible Analytics, where should I turn? I was intrigued by the idea
of server-side analytics. I shouldn’t need clients to run javascript to
know who’s visiting my website, I should be able to tell who’s connecting to my
server and what page they’re requesting! Even though my blog is a static site, my
hosting provider uses Apache, and they offer access to the logs. That should be all
I need for basic analytics right?!?&lt;/p&gt;

&lt;h2 id="awstats"&gt;AWStats&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://awstats.sourceforge.io/"&gt;AWStats&lt;/a&gt; is a great tool! Despite the first
three letters, AWStats has nothing to do with AWS. In fact, it was created six
years &lt;em&gt;before&lt;/em&gt; AWS, in May, 2000. A time when PHP, ASP, Perl, ColdFusion, and
JSP were the some of the most popular server-side languages. AWStats stands for
Advanced Web Statistics. It’s a log analyzer for Apache (and nginx and other)
log files. Whether you’re serving static HTML through Apache or using something
like mod_php, AWStats will parse your server log files and provide info and
statistics about your web requests and visitors. It’s designed to be run on your
web server and hosted by Apache – sort of like an admin panel for your web
site. It has a separate process that can run on a cron schedule to ingest new
log files, and it uses Apache to serve the reports as web pages. I wondered if I
could use AWStats to analyze the Apache log files for my static site!&lt;/p&gt;

&lt;p&gt;Normally, AWStats expects to be run on a web server. It expects to save its
configuration in &lt;code class="language-plaintext highlighter-rouge"&gt;/etc/awstats/awstats.mysite.conf&lt;/code&gt; and it expects to create a
database in &lt;code class="language-plaintext highlighter-rouge"&gt;/var/lib/awstats&lt;/code&gt;. I &lt;em&gt;could&lt;/em&gt; run AWStats on my hosting provider,
but I’d prefer not to. For one, I’m currently only hosting static pages, and
don’t want to set up (or pay for) AWStats to run CGI Perl scripts. Also, because
I pay (pennies) for (megabytes of) server storage, and in the spirit of doing
things frugally, I don’t want to leave all my logs on the server indefinitely.
And finally, I’m the only one that needs to see the reports. I don’t want them
available on the internet, and don’t want to deal with locking down my
configuration to prevent public access. As an alternative to all this, I could
install AWStats on my laptop. This would be easy since I use Ubuntu – just &lt;code class="language-plaintext highlighter-rouge"&gt;sudo
apt install awstats&lt;/code&gt; and I’m done. But I don’t really like that solution either.
AWStats expects to be on a web server, and I don’t want to deal with a bunch of
special (and tricky to reproduce) web server config on my laptop. Docker to the
rescue!&lt;/p&gt;

&lt;p&gt;With Docker, I can mount my log files into a (temporary) AWStats container
running on my laptop to parse my log files and serve reports. I mount the log
files into the container and run the AWStats script to ingest log files. I can
save state between runs by persisting &lt;code class="language-plaintext highlighter-rouge"&gt;/var/lib/awstats&lt;/code&gt; in a Docker volume. And
I can run Apache from the container to view my reports! (AWStats &lt;em&gt;can&lt;/em&gt; generate
static HTML to be viewed without running Apache, but only for a single month at
a time so I find it much nicer to let Apache run the CGI script and change the
time frame as I please.) I started using this AWStats Docker container to view
my server statistics a few weeks ago, and I’m really happy with it! AWStats
produces really useful reports, and I have all the info I need about traffic to
my site without running any analytics javascript on clients! Perhaps more
importantly, it includes a section showing statistics about my error status codes
and which URLs produced the error.&lt;/p&gt;

&lt;h2 id="awstats-docker-setup--configuration"&gt;AWStats Docker Setup &amp;amp; Configuration&lt;/h2&gt;

&lt;p&gt;Here’s my current setup. For a long time, I’ve used a Rakefile (a Makefile, but
with Ruby) to build and deploy my site. I added a task to my Rakefile to grab
the access logs off the server and save them to my laptop (where I keep them and
back them up). I also added tasks to ingest the logs into the Docker container
(with the AWStats database persisted on a Docker volume) and a task to serve the
reports. (I’m using the &lt;a href="https://github.com/pabra/docker_awstats/"&gt;pabra&lt;/a&gt;
AWStats Docker image, which appears to be the most well-maintained an
up-to-date.) Here’s what that looks like.  (Most of this is just bash, so could
be easily ported to a Makefile or another script.)&lt;/p&gt;

&lt;div class="language-ruby highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="n"&gt;desc&lt;/span&gt; &lt;span class="s1"&gt;'Fetch logs from the server'&lt;/span&gt;
&lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;:logs&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'======== FETCH LOGS ========'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bold&lt;/span&gt;
    &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s2"&gt;"scp mikekasberg.com:/home/logs/access_log &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;__dir__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/logs/access.log"&lt;/span&gt;
    &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;__dir__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/logs/access.log"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10000000&lt;/span&gt; &lt;span class="c1"&gt;# 10MB&lt;/span&gt;
        &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Size exceeds 10MB. Rotating log file to local only."&lt;/span&gt;
        &lt;span class="n"&gt;date_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getutc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;iso8601&lt;/span&gt;
        &lt;span class="no"&gt;FileUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;__dir__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/logs/access.log"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;__dir__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/logs/access_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;date_string&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.log"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s2"&gt;"ssh mikekasberg.com 'rm /home/logs/access_log'"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;desc&lt;/span&gt; &lt;span class="s1"&gt;'Ingest logs for AWStats Docker'&lt;/span&gt;
&lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;:awstats_ingest&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'========== INGEST =========='&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bold&lt;/span&gt;
    &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'docker volume create awstats-db-mike-jekyll'&lt;/span&gt;
    &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s2"&gt;"docker run --rm -e AWSTATS_CONF_LOGFILE='logresolvemerge.pl /logs/*.log |' -e AWSTATS_CONF_LOGFORMAT=1 -e AWSTATS_CONF_SITEDOMAIN=www.mikekasberg.com -e AWSTATS_CONF_HOSTALIASES='localhost 127.0.0.1' -e AWSTATS_CONF_ALLOWFULLYEARVIEW=3 -v ${PWD}/logs/:/logs/:ro -v awstats-db-mike-jekyll:/var/lib/awstats pabra/awstats:7.9 awstats.pl -config=awstats  -update"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;desc&lt;/span&gt; &lt;span class="s1"&gt;'Run AWStats Interface'&lt;/span&gt;
&lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;:awstats&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:awstats_ingest&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'========== AWStats =========='&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bold&lt;/span&gt;
    &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'xdg-open "http://localhost:8080/awstats.pl"'&lt;/span&gt;
    &lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"docker run --rm -it -p 8080:80 -e AWSTATS_CONF_SITEDOMAIN=www.mikekasberg.com -e AWSTATS_CONF_HOSTALIASES='localhost 127.0.0.1' -e AWSTATS_CONF_ALLOWFULLYEARVIEW=3 -v awstats-db-mike-jekyll:/var/lib/awstats pabra/awstats:7.9"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Grabbing logs from my server is as easy as running &lt;code class="language-plaintext highlighter-rouge"&gt;rake logs&lt;/code&gt;, and I do that
at least every couple months. And viewing my website statistics is as easy as
running &lt;code class="language-plaintext highlighter-rouge"&gt;rake awstats&lt;/code&gt;. (The &lt;code class="language-plaintext highlighter-rouge"&gt;awstats&lt;/code&gt; task depends on the ingestion task, so it
will ingest any new logs first.) I even added a line that will automatically
open the reports in my web browser so I don’t need to remember the URL!
(&lt;code class="language-plaintext highlighter-rouge"&gt;xdg-open&lt;/code&gt; works on Linux, on macOS you could just use &lt;code class="language-plaintext highlighter-rouge"&gt;open&lt;/code&gt;.) What can you do
with AWStats? It does the basics well (visitors and page views). Beyond that,
it’s great for looking at the most common error URLs, and it’s also good for
looking at referrers.&lt;/p&gt;

&lt;p&gt;AWStats is really old technology, but it’s really good at what it does and it’s
reliable! There are lots of potential problems with javascript analytics but
perhaps the most important is this: Users don’t like it and many try to block
it. I think server-side analytics is a great (obvious?) alternative to
client-side analytics that’s less intrusive, and could be a great fit for small
websites and blogs. Running AWStats in a temporary Docker container is a great
way to get web statistics from server logs!&lt;/p&gt;</description><author>Mike Kasberg's Blog</author><pubDate>Sat, 09 Dec 2023 23:00:00 GMT</pubDate><guid isPermaLink="true">https://www.mikekasberg.com/blog/2023/12/09/analyzing-static-website-logs-with-awstats.html</guid></item><item><title>Steam Deck OLED: Wi-Fi, Streaming, Games</title><link>https://ishan.co/steam-deck/</link><description>&lt;p&gt;I recently got a Steam Deck for my birthday. Ever since its initial launch, I was on the fence about getting one. The first reason was that I already had a handheld in Nintendo Switch. And second, my top played game genre is Real-time Strategy (RTS), which doesn&amp;rsquo;t really work that well on a handheld.&lt;/p&gt;
&lt;p&gt;After using it for a week, I am a big fan of the device. This article is my collection of tips you may find useful and some interesting use cases you&amp;rsquo;ll want to consider before buying one.&lt;/p&gt;</description><author>Ishan Sharma</author><pubDate>Sat, 09 Dec 2023 20:30:00 GMT</pubDate><guid isPermaLink="true">https://ishan.co/steam-deck/</guid></item><item><title>Quoting Andrej Karpathy</title><link>https://nicolaiarocci.com/quoting-andrej-karpathy/</link><description>&lt;blockquote&gt;
&lt;p&gt;I always struggle a bit with I&amp;rsquo;m asked about the &amp;ldquo;hallucination problem&amp;rdquo; in LLMs. Because, in some sense, hallucination is all LLMs do. They are dream machines.&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;We direct their dreams with prompts. The prompts start the dream, and based on the LLM&amp;rsquo;s hazy recollection of its training documents, most of the time the result goes someplace useful.&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;It&amp;rsquo;s only when the dreams go into deemed factually incorrect territory that we label it a &amp;ldquo;hallucination&amp;rdquo;. It looks like a bug, but it&amp;rsquo;s just the LLM doing what it always does.&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Sat, 09 Dec 2023 08:05:25 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/quoting-andrej-karpathy/</guid></item><item><title>Artificial Intelligence Reading List</title><link>https://zacs.site/blog/artificial-intelligence-reading-list.html</link><description>&lt;p&gt;Thanks in no small part to OpenAI&amp;#8217;s &lt;a href="https://chat.openai.com"&gt;ChatGPT&lt;/a&gt;, the past year has seen an explosion in interest in artificial intelligence in general, and in large language models in particular. That democratization of access turned this niche research area into a common topic of conversation, and has led to a lot of fascinating writing on the subject. Although certainly not comprehensive, this article collects some of my favorite articles, papers, and resources into a single reading list.&lt;/p&gt;
                &lt;p&gt;&lt;a href="https://zacs.site/blog/artificial-intelligence-reading-list.html"&gt;Permalink.&lt;/a&gt;&lt;/p&gt;</description><author>Zac Szewczyk</author><pubDate>Sat, 09 Dec 2023 05:03:26 GMT</pubDate><guid isPermaLink="true">https://zacs.site/blog/artificial-intelligence-reading-list.html</guid></item><item><title>Santa Claus is not a god... yet</title><link>https://taylor.town/santa-claus-god</link><description>To dismiss a god of children is unfair to everybody who was ever a child.</description><author>taylor.town</author><pubDate>Sat, 09 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/santa-claus-god</guid></item><item><title>Goals for 2024</title><link>https://olivergilan.com/goals-for-2024/</link><description>Taking stock of my progress.</description><author>Oliver Gilan</author><pubDate>Sat, 09 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://olivergilan.com/goals-for-2024/</guid></item><item><title>Internet Usage</title><link>https://foreverliketh.is/blog/internet-usage/</link><description>&lt;details&gt;
Image Details &amp; Source
&lt;p&gt;&lt;a href="https://unsplash.com/photos/man-walking-beside-man-holding-umbrella-fvmMDEvInLE"&gt;man walking beside man holding umbrella&lt;/a&gt; by &lt;a href="https://unsplash.com/@fraenkly"&gt;Frank Holleman&lt;/a&gt; on &lt;a href="https://unsplash.com/"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://foreverliketh.is/blog/internet-usage/monk-and-dude.jpg"&gt;Click Here for the Original, High-Quality Image&lt;/a&gt;&lt;/p&gt;
&lt;/details&gt;
&lt;hr /&gt;
&lt;p&gt;This page was last modified: &lt;strong&gt;December 13th, 2023&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h5 id="consequences"&gt;Consequences &lt;a class="anchor" href="#consequences"&gt;#&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;Back in August, &lt;a href="https://foreverliketh.is/blog/2023/"&gt;I mentioned&lt;/a&gt; wanting to &lt;a href="https://en.wiktionary.org/wiki/touch_grass"&gt;&amp;ldquo;touch grass&amp;rdquo;&lt;/a&gt; more. On the off chance I need to justify that goal for you, reader, consider the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The phrase &amp;ldquo;terminally online&amp;rdquo; has its own &lt;a href="https://en.wikipedia.org/wiki/Extremely_online"&gt;Wikipedia page&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;People, myself included, have (ironically) taken to calling the &lt;a href="https://en.wikipedia.org/wiki/Ted_Kaczynski"&gt;Unabomber: &amp;ldquo;Uncle Ted&amp;rdquo;&lt;/a&gt; as if, while &amp;ldquo;eccentric&amp;rdquo;, he was maybe &lt;a href="https://sizeof.cat/project/ted-kaczynski-industrial-society-and-its-future/"&gt;&amp;ldquo;onto something&amp;rdquo; / &amp;ldquo;looking out for us&amp;rdquo;&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Anyone unable to recognize that the &lt;a href="https://en.wikipedia.org/wiki/Information_Age"&gt;digital age&lt;/a&gt; has got life out of whack&amp;ndash; well, as my students might say: &amp;ldquo;they&amp;rsquo;re an NPC [&lt;a href="https://en.wikipedia.org/wiki/NPC_(meme)"&gt;#1&lt;/a&gt; &amp;amp; &lt;a href="https://suragu.net/blog/20220913"&gt;#2&lt;/a&gt;]&amp;rdquo;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In that vein, I&amp;rsquo;d like to give my two cents on a prevalent sentiment you&amp;rsquo;re likely to find on your expeditions across cyberspace:&lt;/p&gt;
&lt;p&gt;The thought that &amp;ldquo;true freedom / &lt;a href="https://en.wikipedia.org/wiki/Peace"&gt;peace&lt;/a&gt; / &lt;a href="https://en.wikipedia.org/wiki/Happiness"&gt;happiness&lt;/a&gt; / &lt;a href="https://en.wikipedia.org/wiki/Contentment"&gt;contentment&lt;/a&gt;&amp;rdquo; can only be found in &lt;a href="https://forum.agoraroad.com/index.php?threads/is-leaving-the-internet-the-end-goal.4383/"&gt;abandoning the Internet entirely&lt;/a&gt;. That anything &amp;ldquo;less&amp;rdquo; is a &amp;ldquo;&lt;a href="https://voicedrew.xyz/articles/try-your-hardest.html"&gt;second rate sacrifice&lt;/a&gt;&amp;rdquo;; or a &amp;ldquo;cope [&lt;a href="https://en.wikipedia.org/wiki/Coping"&gt;#1&lt;/a&gt; &amp;amp; &lt;a href="https://suragu.net/blog/20231202"&gt;#2&lt;/a&gt;]&amp;rdquo;.&lt;/p&gt;
&lt;h5 id="some-96-lives"&gt;Some 96 Lives &lt;a class="anchor" href="#some-96-lives"&gt;#&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;I don&amp;rsquo;t remember where I read it, but there&amp;rsquo;s an idea that&amp;rsquo;s stuck with me for over a decade now. It&amp;rsquo;s a sort-of criticism directed at &lt;a href="https://en.wikipedia.org/wiki/Monasticism"&gt;monasticism (or &amp;ldquo;monkhood&amp;rdquo;)&lt;/a&gt;, the concept of renouncing &amp;ldquo;worldly&amp;rdquo; pursuits to devote oneself fully to spiritual work. You know the trope, visitor: some rando experiencing an &lt;a href="https://en.wikipedia.org/wiki/Existential_crisis"&gt;existential crisis&lt;/a&gt; drops everything to join some monastery on some isolated mountain to search for some&amp;hellip; thing.&lt;/p&gt;
&lt;p&gt;Now, I don&amp;rsquo;t mean to repudiate an entire way of life, but the idea challenges that notion and its prerequisites, like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Seclusion"&gt;seclusion&lt;/a&gt; from society / modernity&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;giving things up, &lt;a href="https://en.wikipedia.org/wiki/Renunciation"&gt;renunciation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Solitude"&gt;solitude&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Silence"&gt;silence&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It questions whether the people willing to go to such lengths, are, exclusively, the &lt;em&gt;&lt;strong&gt;ONLY ones&lt;/strong&gt;&lt;/em&gt; worthy of receiving the fruit it bears. It asks if &lt;a href="https://en.wiktionary.org/wiki/Joe_Nobody"&gt;Joe Nobody&lt;/a&gt;, irrespective of what they do, would be undeserving of the same reward. It posits: Which is more difficult? Attaining &amp;ldquo;spiritual wholeness&amp;rdquo; in an environment designed for its cultivation? Or attaining it &lt;em&gt;&lt;strong&gt;in spite&lt;/strong&gt;&lt;/em&gt; of an &amp;ldquo;imperfect&amp;rdquo; situation? That if Mr(s). Nobody can strive towards balance in the midst of their everyday life, with all of its obstacles and struggles, then aren&amp;rsquo;t they &lt;em&gt;&lt;strong&gt;more&lt;/strong&gt;&lt;/em&gt; worthy than the recluse?&lt;/p&gt;
&lt;h5 id="cowardice"&gt;Cowardice? &lt;a class="anchor" href="#cowardice"&gt;#&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;Isn&amp;rsquo;t scurrying off to a desolate convent somewhat akin to thinking it&amp;rsquo;s:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the world&amp;rsquo;s fault!&lt;/li&gt;
&lt;li&gt;my country&amp;rsquo;s fault!&lt;/li&gt;
&lt;li&gt;my city&amp;rsquo;s fault!&lt;/li&gt;
&lt;li&gt;my employer&amp;rsquo;s fault!&lt;/li&gt;
&lt;li&gt;my possessions&amp;rsquo; fault!&lt;/li&gt;
&lt;li&gt;my friend&amp;rsquo;s fault!&lt;/li&gt;
&lt;li&gt;my family&amp;rsquo;s fault!&lt;/li&gt;
&lt;li&gt;the industrial revolution&amp;rsquo;s fault!&lt;/li&gt;
&lt;li&gt;the Internet&amp;rsquo;s fault!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Do you lack the courage or will to take responsibility for your own fulfillment? The determination or discipline to face &lt;a href="https://en.wikipedia.org/wiki/An_Essay_on_Criticism"&gt;your humanity and its err&lt;/a&gt;, your circumstances and its stacked odds&amp;hellip; the Internet and its attempts to control you?&lt;/p&gt;
&lt;p&gt;Look, there&amp;rsquo;s &lt;a href="https://en.wiktionary.org/wiki/all_roads_lead_to_Rome"&gt;many roads to Rome&lt;/a&gt;, traveler. I think, on the whole, we&amp;rsquo;re out of homeostasis; and that you should do &lt;em&gt;&lt;strong&gt;whatever&lt;/strong&gt;&lt;/em&gt; you think best to restore your own personal equilibrium. You feel that means &amp;ldquo;quitting the internet&amp;rdquo;? Go for it, yo.&lt;/p&gt;
&lt;p&gt;Just consider that you may, perhaps, be going from one extreme, to another.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Contributors: &lt;a href="https://en.wikipedia.org/wiki/Alexander_Pope"&gt;Alexander Pope&lt;/a&gt; | &lt;a href="https://en.wikipedia.org/wiki/Augustus"&gt;Augustus&lt;/a&gt; | &lt;a href="https://forum.agoraroad.com/index.php"&gt;Illuminati Pirate&lt;/a&gt; | &lt;a href="https://basementcommunity.com/"&gt;orchids / glacial_pace&lt;/a&gt; | &lt;a href="https://en.wikipedia.org/wiki/Rajneesh"&gt;Osho&lt;/a&gt; | &lt;a href="https://sizeof.cat/"&gt;sizeof.cat&lt;/a&gt; | &lt;a href="https://www.youtube.com/c/SomaSpice/videos"&gt;SomaSpice&lt;/a&gt; | &lt;a href="https://suragu.net/"&gt;suragu&lt;/a&gt; | &lt;a href="https://en.wikipedia.org/wiki/Ted_Kaczynski"&gt;Ted Kaczynski&lt;/a&gt; | &lt;a href="https://voicedrew.xyz/"&gt;Voicedrew&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://indieweb.org/syndication-models"&gt;Syndication&lt;/a&gt;: Agora Road [&lt;a href="https://forum.agoraroad.com/index.php?threads/agora-road-travelogue-december.6246/post-94099"&gt;#1&lt;/a&gt; &amp;amp; &lt;a href="https://forum.agoraroad.com/index.php?threads/is-leaving-the-internet-the-end-goal.4383/post-94100"&gt;#2&lt;/a&gt;] | &lt;a href="https://basementcommunity.com/threads/231?postID=1800#1800"&gt;basement community&lt;/a&gt; | &lt;a href="https://news.indieweb.org/en/foreverliketh.is/blog/internet-usage/"&gt;IndieNews&lt;/a&gt; | &lt;a href="https://indieweb.social/@accordionpolar/111552888381168686"&gt;Mastodon&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This post is part of the basement community 1-Year Anniversary Blog Event [&lt;a href="https://basementcommunity.com/threads/231"&gt;#1&lt;/a&gt; &amp;amp; #2].&lt;/p&gt;
&lt;p&gt;This blog post is also part of Agora Road Travelogue - Dec [&lt;a href="https://forum.agoraroad.com/index.php?threads/agora-road-travelogue-december.6246/"&gt;#1&lt;/a&gt; &amp;amp; #2]. A project attempting to encourage individuals to both write and exercise ownership over their content.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id="comments"&gt;Comments &lt;a class="anchor" href="#comments"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Email: &lt;a href="mailto:hi@foreverliketh.is"&gt;hi@foreverliketh.is&lt;/a&gt;&lt;/p&gt;

&lt;div id="fastcomments-widget"&gt;&lt;/div&gt;

&lt;details&gt;
IndieWeb h-entry
&lt;article class="h-entry"&gt;
&lt;h1 class="p-name"&gt;Internet Usage&lt;/h1&gt;
&lt;p&gt;Published by &lt;a class="p-author h-card" href="https://foreverliketh.is/"&gt;foreverliketh.is&lt;/a&gt;
on &lt;time class="dt-published" datetime="2023-12-09 10:00:00"&gt;9&lt;sup&gt;th&lt;/sup&gt; December 2023&lt;/time&gt;&lt;/p&gt;
&lt;p class="p-summary"&gt;All or Nothing&lt;/p&gt;
&lt;div class="e-content"&gt;
&lt;p&gt;Consequences&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;a class="u-url" href="https://foreverliketh.is/blog/internet-usage/"&gt;Internet Usage&lt;/a&gt;
&lt;/div&gt;
&lt;div&gt;
Agora Road [&lt;a class="u-syndication" href="https://forum.agoraroad.com/index.php?threads/agora-road-travelogue-december.6246/post-94099" rel="syndication"&gt;#1&lt;/a&gt; &amp;
&lt;a class="u-syndication" href="https://forum.agoraroad.com/index.php?threads/is-leaving-the-internet-the-end-goal.4383/post-94100" rel="syndication"&gt;#2&lt;/a&gt;] |
&lt;a class="u-syndication" href="https://basementcommunity.com/threads/231?postID=1800#1800" rel="syndication"&gt;basement community&lt;/a&gt; |
&lt;a class="u-syndication" href="https://news.indieweb.org/en" rel="syndication"&gt;IndieNews&lt;/a&gt; |
&lt;a class="u-syndication" href="https://indieweb.social/@accordionpolar/111552888381168686" rel="syndication"&gt;Mastodon&lt;/a&gt;
&lt;/div&gt;
&lt;a class="p-category" href="https://foreverliketh.is/categories/psychology/"&gt;Psychology&lt;/a&gt;
&lt;/article&gt;
&lt;/details&gt;</description><author>foreverliketh.is</author><pubDate>Sat, 09 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://foreverliketh.is/blog/internet-usage/</guid></item><item><title>A Quick and Brutal Match</title><link>https://lagomor.ph/indieweb/20231208/1515/</link><description>&lt;p&gt;I&amp;rsquo;ve had the ability to post chess games on this blog for a while but have found few games I have really wanted to highlight - this one is one I&amp;rsquo;m proud of.&lt;/p&gt;
&lt;p&gt;Hopefully I&amp;rsquo;ll be willing to start posting &lt;em&gt;losses&lt;/em&gt; I&amp;rsquo;m proud of, too.
&lt;div&gt;
 
&lt;/div&gt;
&lt;/p&gt;</description><author>Home on Lagomorph</author><pubDate>Sat, 09 Dec 2023 00:15:00 GMT</pubDate><guid isPermaLink="true">https://lagomor.ph/indieweb/20231208/1515/</guid></item><item><title>Advent of Code 2023: Building Intuition with Quadratic Equations</title><link>https://epiccoleman.com/posts/2023-12-08-aoc-2023-quadratic-eqs.html</link><description>I take a deep dive into some math I haven't touched for a long time, and use it to solve an Advent of Code problem.</description><author>epiccoleman.com</author><pubDate>Fri, 08 Dec 2023 20:49:13 GMT</pubDate><guid isPermaLink="true">https://epiccoleman.com/posts/2023-12-08-aoc-2023-quadratic-eqs.html</guid></item><item><title>Suggested readings week 49/2023</title><link>https://www.stefanocosta.org/suggested-readings-week-49-2023/</link><description>&lt;hr /&gt;&lt;h3 id="the-inside-story-of-chatgpt"&gt;&lt;a href="https://www-technologyreview-com.cdn.ampproject.org/c/s/www.technologyreview.com/2023/03/03/1069311/inside-story-oral-history-how-chatgpt-built-openai/amp/?ref=stefanocosta.org" rel="nofollow noreferrer noopener"&gt;The inside story of ChatGPT&lt;/a&gt;&lt;/h3&gt;&lt;blockquote&gt;The inside story of how ChatGPT was built from the people who made it&lt;/blockquote&gt;&lt;p&gt;At the center of technology and research is people who dare building it.&lt;/p&gt;&lt;h3 id="decades-old-privacy-problem-solved"&gt;&lt;a href="https://nautil.us/cryptographers-solve-decades-old-privacy-problem-444899/?ref=stefanocosta.org" rel="nofollow noreferrer noopener"&gt;Decades-Old Privacy Problem solved&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;An advancement that could lead to performing searches over the Internet in fully&lt;/p&gt;</description><author>Food for Thought (by Stefano Costa)</author><pubDate>Fri, 08 Dec 2023 15:15:59 GMT</pubDate><guid isPermaLink="true">https://www.stefanocosta.org/suggested-readings-week-49-2023/</guid></item><item><title>Llama Guard leaves things to be desired</title><link>https://friendshipcastle.zip/blog/llamaguard?utm_campaign=feed&amp;utm_source=blog.rss</link><description>Or: it won't filter how to make a pipe bomb</description><author>Twilight Sparkle's Friendship Castle</author><pubDate>Fri, 08 Dec 2023 15:00:00 GMT</pubDate><guid isPermaLink="true">https://friendshipcastle.zip/blog/llamaguard?utm_campaign=feed&amp;utm_source=blog.rss</guid></item><item><title>Capturing decisions and those involved</title><link>https://ilearnt.com/blog/decisionmaking/</link><description>&lt;p&gt;My role has changed recently to include another team. This has also included more interaction with other areas of the business and also the management team.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Fri, 08 Dec 2023 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/decisionmaking/</guid></item><item><title>AWSary on Android</title><link>https://tig.pt/awsary-android/</link><description>&lt;figure class="kg-card kg-bookmark-card"&gt;&lt;a class="kg-bookmark-container" href="https://play.google.com/store/apps/details?id=com.lzcalderaro.awsary&amp;amp;ref=tig.pt"&gt;&lt;div class="kg-bookmark-content"&gt;&lt;div class="kg-bookmark-title"&gt;AWSary - Apps on Google Play&lt;/div&gt;&lt;small&gt;&lt;div class="kg-bookmark-description"&gt;AWS services dictionary&lt;/div&gt;&lt;/small&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p&gt;Finally, AWSary is available on Android.&lt;/p&gt;&lt;p&gt;About 4 Months ago, &lt;a href="https://www.linkedin.com/in/luizcalderaro/?ref=tig.pt"&gt;Luiz Calderaro&lt;/a&gt; approached me to help port AWSary to Android, and I couldn&amp;apos;t be more thrilled.&lt;/p&gt;&lt;p&gt;Luiz has found some time here and there to sync with me and in&lt;/p&gt;</description><author>Tiago Rodrigues</author><pubDate>Fri, 08 Dec 2023 11:27:00 GMT</pubDate><guid isPermaLink="true">https://tig.pt/awsary-android/</guid></item><item><title>How I decarbonized my home for $3,000</title><link>https://pehrlich.substack.com/p/how-i-decarbonized-my-home-for-3000</link><description>From growing up on a small farm in upstate NY, I have vivid memories of holding the blow-torch for my dad on a plumbing project when I was probably 10 years old. He did all the work on the house himself (with the occasional help from an offspring), and so when it came time to electrify my house, I decided to DIY it as well. Here's how it went.</description><author>Into the Details</author><pubDate>Fri, 08 Dec 2023 02:23:26 GMT</pubDate><guid isPermaLink="true">https://pehrlich.substack.com/p/how-i-decarbonized-my-home-for-3000</guid></item><item><title>Working Remotely Using Visual Studio Code</title><link>https://www.tderflinger.com/en/working-remotely-visual-studio-code</link><description>In many circumstances it is convenient to work remotely using Visual Studio Code. There is a fine VS Code extension that allows you to work on a remote machine as if it was your local machine. This is great for things like embedded programming or developing in the cloud. Read here how to do this.</description><author>Thoughts by Thomas Derflinger</author><pubDate>Fri, 08 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.tderflinger.com/en/working-remotely-visual-studio-code</guid></item><item><title>Cambridge has (at least) two parking spaces per household</title><link>https://evanfields.net/Lots-Of-Parking/</link><description>A previous post estimated Cambridge’s non-metered street parking at 31,474 spaces, approximately one per car-owning household. But these spaces constitute a minority of Cambridge’s total parking supply. Here I estimate two other big chunks of our total supply:</description><author>Evan Fields</author><pubDate>Fri, 08 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://evanfields.net/Lots-Of-Parking/</guid></item><item><title>Roast #33: My processes</title><link>https://thoughtfulcoffeenyc.substack.com/p/roast-33-my-processes</link><description>Green buying, sample roasting, and production roasting</description><author>thoughtfulcoffee</author><pubDate>Thu, 07 Dec 2023 19:41:00 GMT</pubDate><guid isPermaLink="true">https://thoughtfulcoffeenyc.substack.com/p/roast-33-my-processes</guid></item><item><title>Breaking things down</title><link>https://ilearnt.com/blog/breakingthingsdown/</link><description>&lt;p&gt;A task can often look too big to tackle. It can become very daunting even in deciding where to start.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Thu, 07 Dec 2023 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/breakingthingsdown/</guid></item><item><title>Cleaning Up Speech Recognition with GPT</title><link>https://blog.nawaz.org/posts/2023/Dec/cleaning-up-speech-recognition-with-gpt/</link><description>&lt;p&gt;My friends often ask for use cases for &lt;span class="caps"&gt;GPT&lt;/span&gt;. Below I provide one recent&amp;nbsp;example.&lt;/p&gt;
&lt;p&gt;I attend a weekly educational seminar where real estate professionals
present the work they do. I take notes on paper, and later transcribe
them to my computer, which is a pain. As a result, I …&lt;/p&gt;</description><author>Beetle Space</author><pubDate>Thu, 07 Dec 2023 10:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2023/Dec/cleaning-up-speech-recognition-with-gpt/</guid></item><item><title>Another Microservice Desaster</title><link>https://backendhance.com/blog/2023/another-microservice-desaster/</link><description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;h1 id="microservice-architektur"&gt;
&lt;a class="anchor" href="#microservice-architektur"&gt;Microservice Architektur&lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;Dieses Buzzword, ist für mich inzwischen ein Trigger geworden. Darüber habe ich schon oft geschrieben.&lt;/p&gt;
&lt;p&gt;Anfang des Jahres trendete mein Artikel &lt;a href="https://backendhance.com/blog/2022/microservices/"&gt;Microservices are a Big Ball of Mud&lt;/a&gt;
auf &lt;a href="https://news.ycombinator.com/item?id=34329656" rel="noopener noreferrer" target="_blank"&gt;Hacker News&lt;i class="bx bx-link-external"&gt;&lt;/i&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;In den 343 Kommentaren zu meinem Artikel kann man gut sehen, wie sehr das Thema die Gemüter erhitzt 😉 Natürlich ist der rationale Blick darauf ein anderer, als ich in dem Artikel dargestellt habe. Es kommt IMMER auf den Kontext an. &lt;a href="https://www.appcontinuum.io/" rel="noopener noreferrer" target="_blank"&gt;AppContinuum&lt;i class="bx bx-link-external"&gt;&lt;/i&gt;&lt;/a&gt;
ist ein hervorragendes Paper zu einem reflektierten Blick auf die Thematik.&lt;/p&gt;</description><author>Backendhance</author><pubDate>Thu, 07 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/another-microservice-desaster/</guid></item><item><title>Advent of Code 2023 in Oracle - Day 7: Camel Cards</title><link>https://hartenfeller.dev/blog/advent-of-code-oracle-2023-day7</link><description>Advent of Code 2023 in Oracle SQL and PL/SQL. Day 7: Camel Cards.</description><author>Philipp Hartenfeller Blog RSS Feed</author><pubDate>Thu, 07 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://hartenfeller.dev/blog/advent-of-code-oracle-2023-day7</guid></item><item><title>Getting Started With PyFlink on Kubernetes</title><link>https://www.morling.dev/blog/getting-started-with-pyflink-on-kubernetes/</link><description>&lt;div class="toc" id="toc"&gt;
&lt;div id="toctitle"&gt;Table of Contents&lt;/div&gt;
&lt;ul class="sectlevel1"&gt;
&lt;li&gt;&lt;a href="#_what_is_pyflink_and_why_should_you_care"&gt;What Is PyFlink and Why Should You Care?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_prerequisites"&gt;Prerequisites&lt;/a&gt;
&lt;ul class="sectlevel2"&gt;
&lt;li&gt;&lt;a href="#_installing_the_flink_kubernetes_operator"&gt;Installing the Flink Kubernetes Operator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_installing_strimzi_and_apache_kafka"&gt;Installing Strimzi and Apache Kafka&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#_a_simple_pyflink_job"&gt;A Simple PyFlink Job&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_building_a_container_image_with_your_pyflink_job"&gt;Building a Container Image With Your PyFlink Job&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_deploying_a_pyflink_job_on_kubernetes"&gt;Deploying a PyFlink Job On Kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;em&gt;This post originally appeared on the &lt;a href="https://www.decodable.co/blog/getting-started-with-pyflink-on-kubernetes"&gt;Decodable blog&lt;/a&gt;. All rights reserved.&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The other day, I wanted to get my feet wet with PyFlink.
While there is a fair amount of related information out there, I couldn’t find really up-to-date documentation on using current versions of PyFlink with Flink on Kubernetes.&lt;/p&gt;
&lt;/div&gt;</description><author>Gunnar Morling</author><pubDate>Thu, 07 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.morling.dev/blog/getting-started-with-pyflink-on-kubernetes/</guid></item><item><title>“We all need our own philosophy”</title><link>https://bowendwelle.substack.com/p/we-all-need-our-own-philosophy</link><description>Bowen Dwelle in conversation with Latham Turner of Get Real, Man on writing to explore, how &amp;#8220;adventure doesn&amp;#8217;t happen by accident,&amp;#8221; writing as men and the transition into being an older man, using research in storytelling, how &amp;#8220;we all need our own philosophy,&amp;#8221; the challenge of positive confrontation and &amp;#8220;the Goat Work,&amp;#8221; how we relate to our immediate geography, long-distance walking, wayfinding, personal spirituality, and the gods we&amp;#8217;re praying as.</description><author>An Ordinary Disaster</author><pubDate>Thu, 07 Dec 2023 00:12:54 GMT</pubDate><guid isPermaLink="true">https://bowendwelle.substack.com/p/we-all-need-our-own-philosophy</guid></item><item><title>What You Missed At My Substack</title><link>https://fredgrott.medium.com/what-you-missed-at-my-substack-97afc0516040?source=rss-2cddef1829fb------2</link><description>&lt;div class="medium-feed-item"&gt;&lt;p class="medium-feed-snippet"&gt;You missed me solving the missing material design 3 carousel from the flutter SDK problem:&lt;/p&gt;&lt;p class="medium-feed-link"&gt;&lt;a href="https://fredgrott.medium.com/what-you-missed-at-my-substack-97afc0516040?source=rss-2cddef1829fb------2"&gt;Continue reading on Medium »&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;</description><author>Stories by Fred Grott on Medium</author><pubDate>Wed, 06 Dec 2023 18:34:49 GMT</pubDate><guid isPermaLink="true">https://fredgrott.medium.com/what-you-missed-at-my-substack-97afc0516040?source=rss-2cddef1829fb------2</guid></item><item><title>Suggested readings week 48/2023</title><link>https://www.stefanocosta.org/suggested-readings-week-48-2023/</link><description>&lt;hr /&gt;&lt;h3 id="steve-blank-and-the-quantum-technology-ecosystem"&gt;&lt;a href="https://steveblank.com/2022/03/22/the-quantum-technology-ecosystem-explained/?ref=stefanocosta.org" rel="nofollow noreferrer noopener"&gt;Steve Blank and the Quantum Technology Ecosystem&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Steve Blank is: E.piphany, Zilog, MIPS Computers, Convergent Technologies, Pixar, Ardent, Rocket Science Games and many other ventures and startups.&lt;/p&gt;&lt;p&gt;He founded his latest startup in his living room and a few years later he retired, in 1999. Since then, Steve Blank&lt;/p&gt;</description><author>Food for Thought (by Stefano Costa)</author><pubDate>Wed, 06 Dec 2023 15:13:30 GMT</pubDate><guid isPermaLink="true">https://www.stefanocosta.org/suggested-readings-week-48-2023/</guid></item><item><title>Decisions with a cost</title><link>https://ilearnt.com/blog/decisionswithacost/</link><description>&lt;p&gt;Sometimes a decision comes with a cost, a sacrifice.&lt;/p&gt;
&lt;p&gt;In the book &lt;a href="https://www.amazon.co.uk/dp/0670923176" target="_blank"&gt;Leaders Eat Last&lt;/a&gt; Simon Sinek says:&lt;/p&gt;</description><author>I Learnt</author><pubDate>Wed, 06 Dec 2023 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/decisionswithacost/</guid></item><item><title>Hosting quickly 2 - Dioxus to the web on Fly.io</title><link>https://blog.martijnarts.com/hosting-quickly-2-dioxus-to-the-web-on-fly-io/</link><description>&lt;p&gt;This is the second article in a series called &amp;quot;Hosting quickly&amp;quot;. I want to launch Rust apps quickly and often, and I want to have stable, common infrastructure for all of them.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.martijnarts.com/hosting-quickly-1-setting-up-terraform-github-and-1password/"&gt;Hosting quickly 1 - Setting up Terraform, Github, and 1Password&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Today, let&amp;apos;s build a frontend&lt;/p&gt;</description><author>Martijn Arts</author><pubDate>Wed, 06 Dec 2023 13:44:23 GMT</pubDate><guid isPermaLink="true">https://blog.martijnarts.com/hosting-quickly-2-dioxus-to-the-web-on-fly-io/</guid></item><item><title>Firefox focus ring regression</title><link>https://seirdy.one/notes/2023/10/31/firefox-focus-ring-regression/</link><description>&lt;p&gt;Firefox 120 appears to have regressed to its older WebKit-like blue focus outlines; it briefly had dual-color white-and-blue outlines.&lt;/p&gt;&lt;p&gt;I’d previously advised certain sites (sites with background colors that have poor contrast against the focus-ring’s blue color) to override the default focus indicators. I planned to temper that advice since Chromium and Firefox adopted focus indicators that appeared visible on a variety of background colors; I suppose I should cancel those plans.&lt;/p&gt;&lt;p&gt;As long as people have to use defaults they didn’t choose (e.g. Tor Browser users are stuck with default settings), we have to override them when they’re inaccessible. We can’t count on users to always have the means to do this: fingerprinting concerns and device-borrowing are facts of life.&lt;/p&gt;&lt;p&gt;&lt;ins datetime="2023-12-06T05:53:35+00:00"&gt;Update &lt;time datetime="2023-12-06T05:53:35+00:00"&gt;2023-12-06&lt;/time&gt;: it appears that as of Firefox 121, the issue only appears in forced-colors mode (e.g. when toggling color overrides in the color settings of &lt;code&gt;about:preferences&lt;/code&gt;). Input elements are unaffected. With forced-colors disabled, Firefox uses the better two-tone indicator.&lt;/ins&gt;&lt;/p&gt;</description><author>All content on Seirdy’s Home</author><pubDate>Wed, 06 Dec 2023 07:53:35 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/notes/2023/10/31/firefox-focus-ring-regression/</guid></item><item><title>Borrowing from big players</title><link>https://seirdy.one/notes/2022/11/02/borrowing-from-big-players/</link><description>&lt;aside&gt;&lt;p&gt;
	Reply to &lt;span class="h-cite u-in-reply-to"&gt;&lt;cite class="p-name"&gt;&lt;a class="u-url" href="https://adrianroselli.com/2020/03/i-dont-care-what-google-or-apple-or-whomever-did.html"&gt;I Don’t Care What Google or Apple or Whoever Did&lt;/a&gt;&lt;/cite&gt; by
		&lt;span class="h-card vcard p-author"&gt;&lt;a class="u-url url" href="https://adrianroselli.com/"&gt;&lt;span class="p-name fn n"&gt;Adrian Roselli&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;
	&lt;/span&gt;
&lt;/p&gt;&lt;/aside&gt;&lt;p&gt;Large organizations’ choices influence my decisions in only one way: by telling me what users are familiar with. For instance: when building a search-results page, it might make sense to borrow the basic semantics of existing search engines (Google, Bing, etc.) so the interface is familiar.&lt;/p&gt;&lt;p&gt;It doesn’t make sense to blatantly violate WCAG (especially at the “A” level!) just because big companies do. The companies you cite know they won’t get sued over link underlines, even though removing them without replacement is an accessibility hazard. Every time someone jumps onto Flutter because Google said it’s accessible, I feel tempted to file &lt;a href="https://github.com/flutter/flutter/issues/94965"&gt;yet another accessibility issue&lt;/a&gt;.&lt;/p&gt;</description><author>All content on Seirdy’s Home</author><pubDate>Wed, 06 Dec 2023 07:50:18 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/notes/2022/11/02/borrowing-from-big-players/</guid></item><item><title>Recovering data from a failed drive using ddrescue</title><link>https://www.skouf.com/posts/data-recovery/</link><description>&lt;p&gt;My first foray into data recovery has been both educational and, blissfully, not too stressful.
You always hear tales of damaged hard drives and complete data loss and think &amp;ldquo;that&amp;rsquo;ll never happen to me&amp;rdquo;.
Well, it finally has happened to me, and thankfully the outcome has been pretty good (all things considered), and I&amp;rsquo;ve
learned a bit along the way.&lt;/p&gt;</description><author>skouf.com</author><pubDate>Wed, 06 Dec 2023 04:09:52 GMT</pubDate><guid isPermaLink="true">https://www.skouf.com/posts/data-recovery/</guid></item><item><title>Talk @ a16z: Taking AI applications to Production</title><link>https://zackproser.com/blog/a16z-sf-dec-2023-ai-apps-production</link><description>I introduced the new Pinecone AWS Reference Architecture with Pulumi and explained infrastructure as code</description><author>Zachary Proser</author><pubDate>Wed, 06 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/a16z-sf-dec-2023-ai-apps-production</guid></item><item><title>An iOS journaling app powered by org plain text</title><link>https://xenodium.com/an-ios-journaling-app-powered-by-org-plain-text</link><description>&lt;p&gt;I've been experimenting with building a rich text editing component for iOS, powered by &lt;a href="https://orgmode.org/"&gt;org&lt;/a&gt; markup. The idea is to offer a mobile-friendly editing experience, backed by our beloved plain text format.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/an-ios-journaling-app-powered-by-org-plain-text/rich-text-experiment.gif" /&gt;&lt;/p&gt;
&lt;p&gt;To make things a little more interesting, I'm introducing a new org-based app to help anyone with regular journaling.&lt;/p&gt;
&lt;center&gt;👉 Meet ✨Journelly✨&lt;/center&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/an-ios-journaling-app-powered-by-org-plain-text/journelly.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Plain text &lt;em&gt;is&lt;/em&gt; the serialization format. No conversion/import/export needed.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/an-ios-journaling-app-powered-by-org-plain-text/journelly.gif" /&gt;&lt;/p&gt;
&lt;p&gt;Though it's early days, it's fairly functional. Been using it daily for some time. You can opt in to use an external org file and sync with your beloved Emacs.&lt;/p&gt;
&lt;p&gt;Want to give it a try? Want a TestFlight invite? Send me an email address (any would do) at either of these: &lt;a href="https://indieweb.social/@xenodium"&gt;Mastodon&lt;/a&gt; / &lt;a href="https://twitter.com/xenodium"&gt;Twitter&lt;/a&gt; / &lt;a href="https://www.reddit.com/user/xenodium"&gt;Reddit&lt;/a&gt; / &lt;a href="mailto:me__AT__xenodium.com"&gt;Email&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The topic of org being fairly Emacs-oriented, though a strength for someone far down the rabbit hole, it is &lt;a href="https://indieweb.social/@ringtailringo@mastodon.social/111533733278287863"&gt;understandable to call it out for someone in a different position&lt;/a&gt;. Lucky for us, org markup is plain text and can be implemented by apps other than Emacs, like Journelly itself for iOS and even more experimentally on macOS:&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/an-ios-journaling-app-powered-by-org-plain-text/macos.png" /&gt;&lt;/p&gt;
&lt;p&gt;And like Journelly for iOS, I got other org things available on iOS:&lt;/p&gt;
&lt;table&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;a href="https://plainorg.com"&gt;&lt;img src="https://plainorg.github.io/favicon.ico" width="40" /&gt;&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;As an &lt;a href="https://orgmode.org/"&gt;Org mode&lt;/a&gt; fan, so I wrote &lt;a href="https://plainorg.com/"&gt;Plain Org&lt;/a&gt; for iOS. It's on the &lt;a href="https://apps.apple.com/app/id1578965002"&gt;App Store&lt;/a&gt;.&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;a href="https://apps.apple.com/app/id1671420139"&gt;&lt;img src="https://raw.githubusercontent.com/xenodium/xenodium.github.io/master/images/scratch-a-minimal-scratch-area/scratch_icon.png" width="40" /&gt;&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;Inspired by &lt;a href="https://jamesclear.com/atomic-habits"&gt;Atomic Habits&lt;/a&gt;, I wrote &lt;a href="https://flathabits.com/"&gt;Flat Habits&lt;/a&gt; for iOS. Also on the &lt;a href="https://apps.apple.com/app/id1558358855"&gt;App Store&lt;/a&gt;.&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;a href="https://flathabits.com"&gt;&lt;img src="https://flathabits.github.io/favicon.ico" width="40" /&gt;&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;I needed an Emacs-inspired &lt;a href="scratch-a-minimal-scratch-area"&gt;&lt;strong&gt;scratch&lt;/strong&gt;&lt;/a&gt; buffer on iOS (who doesn't?), so I &lt;a href="scratch-a-minimal-scratch-area/"&gt;built one&lt;/a&gt;.&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;As an &lt;a href="https://orgmode.org/"&gt;Org mode&lt;/a&gt; fan, so I wrote &lt;a href="https://plainorg.com/"&gt;Plain Org&lt;/a&gt; for iOS. It's on the &lt;a href="https://apps.apple.com/app/id1578965002"&gt;App Store&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Inspired by &lt;a href="https://jamesclear.com/atomic-habits"&gt;Atomic Habits&lt;/a&gt;, I wrote &lt;a href="https://flathabits.com/"&gt;Flat Habits&lt;/a&gt; for iOS. Also on the &lt;a href="https://apps.apple.com/app/id1558358855"&gt;App Store&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;I needed an Emacs-inspired &lt;a href="https://xenodium.com/scratch-a-minimal-scratch-area"&gt;&lt;strong&gt;scratch&lt;/strong&gt;&lt;/a&gt; buffer on iOS (who doesn't?), so I &lt;a href="https://xenodium.com/scratch-a-minimal-scratch-area/"&gt;built one&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Just like the stuff I do or &lt;a href="https://xenodium.com"&gt;write about&lt;/a&gt;? &lt;a href="https://github.com/sponsors/xenodium"&gt;Sponsor me&lt;/a&gt;.&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Wed, 06 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/an-ios-journaling-app-powered-by-org-plain-text</guid></item><item><title>How to Observe Buydays</title><link>https://taylor.town/buyday</link><description>Consolidate non-essential purchases to fewer days.</description><author>taylor.town</author><pubDate>Wed, 06 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/buyday</guid></item><item><title>Make a /wish page!</title><link>https://taylor.town/wish-manifesto</link><description>An honest wishlist lays bare your values, motivations, and desires.</description><author>taylor.town</author><pubDate>Wed, 06 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/wish-manifesto</guid></item><item><title>/wish</title><link>https://taylor.town/wish</link><description>My wishlist is honest and sometimes embarassing.</description><author>taylor.town</author><pubDate>Wed, 06 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/wish</guid></item><item><title>Unlocking the Bootloader on the Kyocera 902KC</title><link>https://lagomor.ph/indieweb/20231205/1530-note/</link><description>&lt;p&gt;There have been &lt;a href="https://www.reddit.com/r/dumbphones/comments/184f2jm/keitai_digno_a203kc_bootloader_unlocked_lets/" rel="noopener noreferrer" target="_blank"&gt;mixed&lt;/a&gt; &lt;a href="https://www.reddit.com/r/dumbphones/comments/179vreg/some_personal_notes_on_configuring_the_kyocera/" rel="noopener noreferrer" target="_blank"&gt;reports&lt;/a&gt; of people managing to unlock the bootloader on the Kyocera 902KC. I&amp;rsquo;ve done some extensive research on unlocking and rooting this phone and I&amp;rsquo;d like to outline all my findings here.&lt;/p&gt;
&lt;p&gt;Being able to unlock the bootloader on the Kyocera 902KC would be a huge boon for usability in North America, as it would mean you could sideload the &lt;a href="https://microg.org/" rel="noopener noreferrer" target="_blank"&gt;microG&lt;/a&gt; services, allowing you to utilize maps properly, as well as any apps that rely on Google Play Services in order to function.&lt;/p&gt;</description><author>Home on Lagomorph</author><pubDate>Wed, 06 Dec 2023 00:30:00 GMT</pubDate><guid isPermaLink="true">https://lagomor.ph/indieweb/20231205/1530-note/</guid></item><item><title>Limiting concurrency with Python Coroutines</title><link>https://www.danielcorin.com/til/python/limiting-concurrency-coroutines/</link><description>Limiting concurrency with Python Coroutines</description><author>Thought Eddies</author><pubDate>Tue, 05 Dec 2023 20:08:00 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/python/limiting-concurrency-coroutines/</guid></item><item><title>Pseudonyms in American History</title><link>https://mattrickard.com/pseudonyms-in-american-history</link><description>Debates around the ratification of the Constitution and the early formation of the United States happened through pseudonymous authors. They often used names bo</description><author>Matt Rickard</author><pubDate>Tue, 05 Dec 2023 16:30:00 GMT</pubDate><guid isPermaLink="true">https://mattrickard.com/pseudonyms-in-american-history</guid></item><item><title>Simple tips can make a difference</title><link>https://ilearnt.com/blog/woodlandphotography/</link><description>&lt;p&gt;When I grew up I had an old 35mm Praktica camera that was given to me. I purchased some lenses for it and mostly used it to take pictures of the Somerset cricket matches I went to watch.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Tue, 05 Dec 2023 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/woodlandphotography/</guid></item><item><title>How to use Visual Studio Code to run and debug code on SLURM compute nodes</title><link>https://e-dorigatti.github.io/development/2023/12/05/vscode-slurm.html</link><description>If you’re a developer or data scientist using SLURM to handle your compute workloads, you have surely encountered issues in debugging your code on compute nodes. In this blog post, I share a simple solution for that, allowing you to develop and debug code running directly with the compute resources you need. Although the focus is on Visual Studio Code, the same approach can be applied to other IDEs that support remote development via SSH.</description><author>Emilio’s Blog</author><pubDate>Tue, 05 Dec 2023 12:00:00 GMT</pubDate><guid isPermaLink="true">https://e-dorigatti.github.io/development/2023/12/05/vscode-slurm.html</guid></item><item><title>Every time you log out…</title><link>https://www.dotcoma.it/2023/12/05/every-time-you-log-out.html</link><description>Every time you log out from GMail, they count you as a YouTube user. So sad to see what rubbish Google has become! Do yourself a favour: Try Tuta !</description><author>English – dotcoma</author><pubDate>Tue, 05 Dec 2023 08:58:57 GMT</pubDate><guid isPermaLink="true">https://www.dotcoma.it/2023/12/05/every-time-you-log-out.html</guid></item><item><title>You Don't Need AI, You Need an Algorithm</title><link>https://zacs.site/blog/you-need-an-algorithm.html</link><description>&lt;p&gt;A few weeks ago, I participated in a working group tasked with identifying areas where artificial intelligence and machine learning could improve network utilization. What, specifically, &amp;#8220;improve&amp;#8221; and &amp;#8220;utilization&amp;#8221; meant were some of the first questions we addressed. During that conversation, though, one of the participants made an insightful observation that the group promptly ignored. He told a brief story about how someone had asked him for a networking device that could aggregate multiple uplinks and then &amp;#8220;use AI to choose the best one.&amp;#8221; When he offered a simple WAN failover solution, a technology that has existed for decades and comes built-in to open source firewalls like pfSense, commercial appliances from the likes of Cisco, and every modern mobile phone, the customer told him, &amp;#8220;No.&amp;#8221; Like our working group, that customer person had a solution (artificial intelligence) driving their requirements (an AI-based WAN failover), not the other way around.&lt;/p&gt;
                &lt;p&gt;&lt;a href="https://zacs.site/blog/you-need-an-algorithm.html"&gt;Permalink.&lt;/a&gt;&lt;/p&gt;</description><author>Zac Szewczyk</author><pubDate>Tue, 05 Dec 2023 05:06:36 GMT</pubDate><guid isPermaLink="true">https://zacs.site/blog/you-need-an-algorithm.html</guid></item><item><title>A "Unit" In A Test Is Not The Class Under Test</title><link>https://backendhance.com/blog/2023/a-unit-in-a-test-is-not-the-class-under-test/</link><description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;h2 id="was-ist-eigentlich-eine-unit-im-unit-test"&gt;
&lt;a class="anchor" href="#was-ist-eigentlich-eine-unit-im-unit-test"&gt;Was ist eigentlich eine &amp;ldquo;Unit&amp;rdquo; im Unit-Test?&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Dumme Frage, sagst du dir vielleicht.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Aber sie ist interessant. Über viele Jahre war ich, wie die meisten die über das Thema sprechen, der Meinung:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Eine Unit ist eine Klasse, die ich testen möchte.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Und vielleicht hast du das so auch gesehen. Und du hast mit Sicherheit damit ein paar Probleme gehabt.&lt;/p&gt;
&lt;h3 id="zum-beispiel-die-symptome-des-daraus-resultierendem-coupling"&gt;
&lt;a class="anchor" href="#zum-beispiel-die-symptome-des-daraus-resultierendem-coupling"&gt;Zum Beispiel, die Symptome des daraus resultierendem Coupling&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Denn was passiert, wenn wir eine &lt;code&gt;UserServiceTest&lt;/code&gt; Klasse schreiben? Wir haben - ohne es zu merken - ein semantisches Coupling zwischen der &lt;code&gt;UserService&lt;/code&gt; Klasse und dem &lt;code&gt;UserServiceTest&lt;/code&gt; hergestellt.&lt;/p&gt;</description><author>Backendhance</author><pubDate>Tue, 05 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/a-unit-in-a-test-is-not-the-class-under-test/</guid></item><item><title>Partner u porodu</title><link>https://vit.baisa.cz/books/partner-u-porodu/</link><description>advice for dads how to be helpful during labour</description><author>Vít Baisa</author><pubDate>Tue, 05 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://vit.baisa.cz/books/partner-u-porodu/</guid></item><item><title>Về trí nhớ trong việc học, và giáo dục phổ thông</title><link>https://peterhung.org/education/ve-tri-nho-trong-viec-hoc-va-giao-duc-pho-thong/</link><description>&lt;p&gt;Gần đây, tôi có một quan sát mới mà trước đây tôi ít khi để ý, đó là về vai trò của trí nhớ trong việc học. Trong bài chia sẻ này, tôi muốn chia sẻ một số suy nghĩ hiện nay của tôi về chủ đề này. Khi học, chúng ta hiểu hay nhớ? &amp;#8230; &lt;a class="more-link" href="https://peterhung.org/education/ve-tri-nho-trong-viec-hoc-va-giao-duc-pho-thong/"&gt;Continue reading &lt;span class="screen-reader-text"&gt;Về trí nhớ trong việc học, và giáo dục phổ thông&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://peterhung.org/education/ve-tri-nho-trong-viec-hoc-va-giao-duc-pho-thong/" rel="nofollow"&gt;Về trí nhớ trong việc học, và giáo dục phổ thông&lt;/a&gt; appeared first on &lt;a href="https://peterhung.org" rel="nofollow"&gt;Peter Hung&lt;/a&gt;.&lt;/p&gt;</description><author>Peter Hung</author><pubDate>Mon, 04 Dec 2023 23:29:07 GMT</pubDate><guid isPermaLink="true">https://peterhung.org/education/ve-tri-nho-trong-viec-hoc-va-giao-duc-pho-thong/</guid></item><item><title>Fairchildren</title><link>https://mattrickard.com/fairchildren</link><description>In 1956, William Shockley, Stanford professor and winner of the Nobel Prize in Physics for his work on semiconductors, recruited a team of young Ph.D. graduates</description><author>Matt Rickard</author><pubDate>Mon, 04 Dec 2023 16:30:00 GMT</pubDate><guid isPermaLink="true">https://mattrickard.com/fairchildren</guid></item><item><title>Turbo Pascal is 40</title><link>https://ilearnt.com/blog/turbopascal/</link><description>&lt;p&gt;Turbo Pascal is &lt;a href="https://blog.marcocantu.com/blog/2023-november-turbopascal40.html" target="_blank"&gt;40 years old&lt;/a&gt; and it had a big impact on the direction of my career.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Mon, 04 Dec 2023 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/turbopascal/</guid></item><item><title>My Selfhosting Setup</title><link>https://tiim.ch/blog/2023-12-03-my-selfhosting-setup</link><description>A short overview on how I self-host.</description><author>Tim Bachmann's Blog</author><pubDate>Mon, 04 Dec 2023 10:00:00 GMT</pubDate><guid isPermaLink="true">https://tiim.ch/blog/2023-12-03-my-selfhosting-setup</guid></item><item><title>#COP28</title><link>https://www.dotcoma.it/2023/12/04/cop28.html</link><description>They&amp;#8217;re holding #COP28 in this dystopic, fucked-up place.</description><author>English – dotcoma</author><pubDate>Mon, 04 Dec 2023 09:08:33 GMT</pubDate><guid isPermaLink="true">https://www.dotcoma.it/2023/12/04/cop28.html</guid></item><item><title>A Consideration of SQL ORMs</title><link>https://gallant.dev/posts/a-consideration-of-sql-orms/</link><description>&lt;p&gt;Conventional wisdom in modern software development generally endorses the use of an ORM.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"Any project that doesn’t use an ORM will end up with a poorly implemented partial ORM."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Most developers are familiar with the dreaded &lt;a href="https://xkcd.com/327/"&gt;SQL injection attack&lt;/a&gt; - an ORM provides solid protection, while exposing a comfortable OOP interface for application developers:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;blog.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Blog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Entry&lt;/span&gt;
&lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cheese_blog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Blog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Cheddar Talk"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cheese_blog&lt;/span&gt;
&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Example from  &lt;a href="https://docs.djangoproject.com/en/4.2/topics/db/queries/"&gt;the Django framework&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But ORMs have a dark side - they hide (but do not reduce) &lt;a href="https://grugbrain.dev"&gt;complexity&lt;/a&gt;, and the interface they expose doesn’t correspond particularly well to the actual work done by the database. SQL is declarative, already obfuscating execution - ORMs hide even more of the computational work behind class abstractions.&lt;/p&gt;
&lt;!-- TEASER_END --&gt;

&lt;p&gt;&lt;img alt="A wizard computer engineer using a terminal to interact with a massive database of global information, rendered via Stable Diffusion in &amp;quot;futuristic-biomechanical cyberpunk&amp;quot; style" src="https://gallant.dev/images/wizard_computer_engineer.png" /&gt;&lt;/p&gt;
&lt;p&gt;When you get into SQL, it is valuable to spend some time with the query planner. Run enough EXPLAIN statements and you start to see how your declarations are actually executed. Table scans, index-less conditions, expensive joins - SQL has a few typical sources of big costs, and you’ll start developing an instinct for them.&lt;/p&gt;
&lt;p&gt;ORMs make it harder to develop that instinct. With an ORM you can retrieve a &lt;a href="https://en.wikipedia.org/wiki/Many-to-many_(data_model)"&gt;many-to-many relationship&lt;/a&gt; in a single line - the coder might not even know that's what they're doing, much less how it is executed in the database. Put some ORM interactions behind utility methods or decorators, have one route that hits a few of those, and you can end up with hundreds of lines of inefficient SQL executed on every page load.&lt;/p&gt;
&lt;p&gt;For many applications, the database is the most critical and heavily-used piece of architecture. Overloading it with inefficient queries (just to let developers use classes and objects instead of SQL) can lead to a very bad place. I’ve seen several ORM-backed applications where typical user flow was significantly slower due to cumbersome data queries generated by the ORM.&lt;/p&gt;
&lt;p&gt;There isn’t a quick fix for this either - removing an ORM is usually untenable, and optimizing without reducing functionality is a painstaking process. Adding a few indices can help - but if what you really need to do is change how your schema is arranged, you're in for a lot of work.&lt;/p&gt;
&lt;p&gt;Meditating on this, I’m leaning towards a somewhat-unconventional-wisdom perspective - prepared statements should be preferred to ORMs for all but the most straightforward &lt;a href="https://en.wikipedia.org/wiki/Create%2C_read%2C_update_and_delete"&gt;CRUD&lt;/a&gt; applications.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;mysql.connector&lt;/span&gt;
&lt;span class="n"&gt;connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connector&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'blog'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prepared&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;get_blog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SELECT id FROM blog WHERE name = &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;update_entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"UPDATE entry SET blog = &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt; WHERE id = &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_blog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Cheddar Talk"&lt;/span&gt;&lt;span class="p"&gt;,))&lt;/span&gt;
&lt;span class="n"&gt;blog_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update_blog_entry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blog_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Based on the above Django example and &lt;a href="https://dev.mysql.com/doc/connector-python/en/connector-python-api-mysqlcursorprepared.html"&gt;the MySQL Python connector&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The specific implementation can vary (as it can with ORMs), but the general principle - make your developers actually write SQL. Have strongly typed parameterization, so static checks still prevent SQL injection. But don’t hide the database more than SQL itself already does - encourage developers to interact with it, and to understand their queries. Admittedly this results in more code (at least for this example) - but also, more understanding.&lt;/p&gt;
&lt;p&gt;The main positive impact is that developers will better understand the performance impact of the code they write. They'll also be better at debugging and ad hoc fixes, as they'll be able to work directly with the database. And if some terrible and inefficient query makes it into the codebase somehow, factoring it out will be much easier than fixing a whole ORM.&lt;/p&gt;
&lt;p&gt;Prepared statements have drawbacks (besides writing SQL, which I’m positioning as a plus) - most notably, they don’t give you a data structure to put your results in, while ORMs involve declaring a class for every table. But there are other &lt;a href="https://docs.pydantic.dev/latest/"&gt;excellent ways to specify typed objects&lt;/a&gt; - and with prepared statements, you only have to do so for classes that are actually useful (and just let the less frequent or more-blobby things live in key-value structures).&lt;/p&gt;
&lt;p&gt;Uncoupling "how it's represented as a business-logic object" from "how it's stored in the database" lets you write classes that are more efficient - taking only what they need, and not forcing tables or relationships in the database to conform to them (which instead can be designed to optimize for the storage and processing of the database).&lt;/p&gt;
&lt;p&gt;Of course, you can do these things with ORMs too - but it involves adding &lt;em&gt;more&lt;/em&gt; code to your system, rather than less. The &lt;a href="https://en.wikipedia.org/wiki/Happy_path"&gt;happy path&lt;/a&gt; of an ORM is when business logic and data storage perfectly align - in reality, this does not occur.&lt;/p&gt;
&lt;p&gt;ORMs do offer some perks - they may claim compatibility with different database backends, and they naturally encourage &lt;a href="https://en.wikipedia.org/wiki/Don't_repeat_yourself"&gt;DRY code&lt;/a&gt; (which requires more discipline with prepared statements). But in my experience, supposedly-backend-agnostic tools only "just work" for trivial cases - and relying on any tool to ensure code quality is always inadequate. Good code is a &lt;a href="https://xkcd.com/844/"&gt;process&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So - next time you're building an application, ask yourself - is what I'm doing super-simple CRUD? If so, go with an ORM! If not, give prepared statements a try. It may take some more planning, but hopefully you'll build a better system - and you'll definitely learn something.&lt;/p&gt;</description><author>~gallant</author><pubDate>Mon, 04 Dec 2023 03:04:13 GMT</pubDate><guid isPermaLink="true">https://gallant.dev/posts/a-consideration-of-sql-orms/</guid></item><item><title>A Look Back on 7 Years of Automating Stuff</title><link>https://jerrynsh.com/a-look-back-on-7-years-of-automating-stuff/</link><description>&lt;p&gt;A little bit more than 7 years into my career, I thought it would be fun to pen down a summary post about my adventure in automating various tiny aspects of my life. Most if not all of the stuff here sprouted from my own problems and itches I needed&lt;/p&gt;</description><author>Jerry Ng</author><pubDate>Mon, 04 Dec 2023 02:00:34 GMT</pubDate><guid isPermaLink="true">https://jerrynsh.com/a-look-back-on-7-years-of-automating-stuff/</guid></item><item><title>Building a Homelab, Part 2 - LDAP and Single Sign-On</title><link>https://blog.janissary.xyz/posts/homelab-2</link><author>janissary</author><pubDate>Mon, 04 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.janissary.xyz/posts/homelab-2</guid></item><item><title>Branchless Lomuto Partitioning</title><link>https://orlp.net/blog/branchless-lomuto-partitioning/</link><description>&lt;p&gt;A partition function accepts as input an array of elements, and a function
returning a bool (a &lt;em&gt;predicate&lt;/em&gt;) which indicates if an element should be in the
first, or second partition. Then it returns two arrays, the two &lt;em&gt;partitions&lt;/em&gt;:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #215da8;"&gt;def &lt;/span&gt;&lt;span&gt;partition(v, pred):
&lt;/span&gt;&lt;span&gt;    first = [x &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;for &lt;/span&gt;&lt;span&gt;x &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;in &lt;/span&gt;&lt;span&gt;v &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;if &lt;/span&gt;&lt;span&gt;pred(x)]
&lt;/span&gt;&lt;span&gt;    second = [x &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;for &lt;/span&gt;&lt;span&gt;x &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;in &lt;/span&gt;&lt;span&gt;v &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;if &lt;/span&gt;&lt;span&gt;not pred(x)]
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;return &lt;/span&gt;&lt;span&gt;first, second
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This can actually be done without needing any extra memory beyond the original
array. An &lt;em&gt;in-place&lt;/em&gt; partition algorithm reorders the array such that all the
elements for which &lt;code&gt;pred(x)&lt;/code&gt; is true come before the elements for which
&lt;code&gt;pred(x)&lt;/code&gt; is false (or vice versa - it doesn’t really matter). Finally by
returning how many elements satisfied &lt;code&gt;pred(x)&lt;/code&gt; to the caller they can then
logically split the array in two slices. This scheme is used in C++’s
&lt;a href="https://en.cppreference.com/w/cpp/algorithm/partition"&gt;&lt;code&gt;std::partition&lt;/code&gt;&lt;/a&gt;
for example.&lt;/p&gt;
&lt;aside&gt;
&lt;p&gt;Usually partitioning is discussed in the context of sorting, most notably
&lt;a href="https://en.wikipedia.org/wiki/Quicksort"&gt;quicksort&lt;/a&gt;. There it is typically used
with a &lt;em&gt;pivot&lt;/em&gt; $p$, and you partition the data into ${x &amp;lt; p}$ and ${x \geq p}$
before recursing on both partitions. However, it is more generally applicable than that.&lt;/p&gt;
&lt;/aside&gt;
&lt;h2 id="in-place-partition-algorithms"&gt;&lt;a class="anchor" href="#in-place-partition-algorithms"&gt;In-place partition algorithms&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are many possible variations / implementations of in-place partition
algorithms, but they usually follow one of two schools: Hoare or Lomuto, named
after their original inventors &lt;a href="https://en.wikipedia.org/wiki/Tony_Hoare"&gt;Tony
Hoare&lt;/a&gt; (the inventor of quicksort),
and Nico Lomuto.&lt;/p&gt;
&lt;h3 id="hoare"&gt;&lt;a class="anchor" href="#hoare"&gt;Hoare&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In Hoare-style partition algorithms you have two iterators scanning the array,
one left-to-right ($i$) and one right-to-left ($j$). The former tries to find
elements that belong on the right, and the latter tries to find elements that
belong on the left. When both iterators have found an element, you swap them,
and continue. When the iterators cross each other you are done.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #215da8;"&gt;def &lt;/span&gt;&lt;span&gt;hoare_partition(v, pred):
&lt;/span&gt;&lt;span&gt;    i = &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0       &lt;/span&gt;&lt;span style="color: #76647b;"&gt;# Loop invariant: all(pred(x) for x in v[:i])
&lt;/span&gt;&lt;span&gt;    j = len(v)  &lt;/span&gt;&lt;span style="color: #76647b;"&gt;# Loop invariant: all(not pred(x) for x in v[j:])
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;while &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;True&lt;/span&gt;&lt;span&gt;:
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;while &lt;/span&gt;&lt;span&gt;i &amp;lt; j and pred(v[i]): i += &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1
&lt;/span&gt;&lt;span&gt;        j -= &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;while &lt;/span&gt;&lt;span&gt;i &amp;lt; j and not pred(v[j]): j -= &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;if &lt;/span&gt;&lt;span&gt;i &amp;gt;= j: &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;return &lt;/span&gt;&lt;span&gt;i
&lt;/span&gt;&lt;span&gt;        v[i], v[j] = v[j], v[i]
&lt;/span&gt;&lt;span&gt;        i += &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The loop invariant can visually be seen as such (using the symbols $&amp;lt;$ and
$\geq$ for the predicate outcomes, as is usual in sorting):&lt;/p&gt;
&lt;p&gt;&lt;img alt="A visual representation of the Hoare loop invariant." src="/blog/branchless-lomuto-partitioning/hoare-invariant.png" /&gt;&lt;/p&gt;
&lt;p&gt;Doing this efficiently is perhaps an article for another day, if you are curious you can check out
the paper &lt;a href="https://arxiv.org/abs/1604.06697"&gt;BlockQuicksort: How Branch Mispredictions don’t affect
Quicksort&lt;/a&gt; by Stefan Edelkamp and Armin Weiß,
which is the technique I implemented in &lt;a href="https://github.com/orlp/pdqsort"&gt;pdqsort&lt;/a&gt;. Another take on the same
idea is found in &lt;a href="https://github.com/minjaehwang/bitsetsort"&gt;bitsetsort&lt;/a&gt;. Key here is that it can be
done &lt;em&gt;branchlessly&lt;/em&gt;. A &lt;em&gt;branch&lt;/em&gt; is a point at which the CPU has to make a choice of which code to
run. The most recognizable form of a branch is the &lt;code&gt;if&lt;/code&gt; statement, but there are others (e.g.
&lt;code&gt;while&lt;/code&gt; conditions, calling a function from a lookup table, short-circuiting logical operators).&lt;/p&gt;
&lt;p&gt;To cut a long story short, CPUs try to predict which piece of code it should run
next and already starts doing it even before it knows if the code it is sending
down the &lt;a href="https://en.wikipedia.org/wiki/Instruction_pipelining"&gt;pipeline&lt;/a&gt; is the
right choice. This is great in most cases as most branches are easy to predict,
but it does incur a penalty when the prediction was wrong, as the CPU needs to
stop, go back and restart from the right point once it realizes it was wrong
(which can take a while).&lt;/p&gt;
&lt;p&gt;Especially in sorting when the outcomes of comparisons are ideally unpredictable
(the more unpredictable the outcome of a comparison, the &lt;a href="https://en.wikipedia.org/wiki/Binary_entropy_function"&gt;more informative&lt;/a&gt;
getting the answer is), it can thus be advisable to avoid branching on comparisons
altogether.&lt;/p&gt;
&lt;aside&gt;
&lt;p&gt;Another branchless partition algorithm that is similar to Hoare but which makes
a temporary gap in the array so it can use moves rather than swaps is the &lt;em&gt;fulcrum
partition&lt;/em&gt; found in &lt;a href="https://github.com/scandum/crumsort"&gt;crumsort&lt;/a&gt; by Igor
van den Hoven.&lt;/p&gt;
&lt;/aside&gt;
&lt;h3 id="lomuto"&gt;&lt;a class="anchor" href="#lomuto"&gt;Lomuto&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In Lomuto-style partition algorithms the following invariant is followed:&lt;/p&gt;
&lt;p&gt;&lt;img alt="A visual representation of the Lomuto loop invariant." src="/blog/branchless-lomuto-partitioning/lomuto-invariant.png" /&gt;&lt;/p&gt;
&lt;p&gt;That is, there is a single iterator scanning the array from left-to-right ($j$). If
the element is found to belong in the left partition, it is swapped with the
first element of the right partition (tracked by $i$), otherwise it is left
where it is.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #215da8;"&gt;def &lt;/span&gt;&lt;span&gt;lomuto_partition(v, pred):
&lt;/span&gt;&lt;span&gt;    i = &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0  &lt;/span&gt;&lt;span style="color: #76647b;"&gt;# Loop invariant: all(pred(x) for x in v[:i])
&lt;/span&gt;&lt;span&gt;    j = &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0  &lt;/span&gt;&lt;span style="color: #76647b;"&gt;# Loop invariant: all(not pred(x) for x in v[i:j])
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;while &lt;/span&gt;&lt;span&gt;j &amp;lt; len(v):
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;if &lt;/span&gt;&lt;span&gt;pred(v[j]):
&lt;/span&gt;&lt;span&gt;            v[i], v[j] = v[j], v[i]
&lt;/span&gt;&lt;span&gt;            i += &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1
&lt;/span&gt;&lt;span&gt;        j += &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;return &lt;/span&gt;&lt;span&gt;i
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This article is focused on optimizing this style of partition.&lt;/p&gt;
&lt;h2 id="branchless-lomuto"&gt;&lt;a class="anchor" href="#branchless-lomuto"&gt;Branchless Lomuto&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A few years ago I read &lt;a href="https://dlang.org/blog/2020/05/14/lomutos-comeback/"&gt;a post&lt;/a&gt;
by Andrei Alexandrescu which discusses a branchless variant of the Lomuto
partition. Its inner loop (in C++) looks like this:&lt;/p&gt;
&lt;pre class="language-cpp " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-cpp"&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;for &lt;/span&gt;&lt;span&gt;(; read &amp;lt; last; ++read) {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #215da8;"&gt;auto&lt;/span&gt;&lt;span&gt; x = *read;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #215da8;"&gt;auto&lt;/span&gt;&lt;span&gt; smaller = -&lt;/span&gt;&lt;span style="color: #215da8;"&gt;int&lt;/span&gt;&lt;span&gt;(x &amp;lt; pivot);
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #215da8;"&gt;auto&lt;/span&gt;&lt;span&gt; delta = smaller &amp;amp; (read - first);
&lt;/span&gt;&lt;span&gt;    first[delta] = *first;
&lt;/span&gt;&lt;span&gt;    read[-delta] = x;
&lt;/span&gt;&lt;span&gt;    first -= smaller;
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At the time I was not overly impressed, as it does a lot of arithmetic to make
the loop branchless, so I disregarded it. A while back my friend &lt;a href="https://github.com/voultapher/"&gt;Lukas
Bergdoll&lt;/a&gt; approached me with a new partition
algorithm which was doing quite well in his benchmarks, which I recognized as
being a variant of Lomuto. I then found a way the algorithm could be
restructured without using conditional move instructions, which made it perform
better still. I will present this algorithm now.&lt;/p&gt;
&lt;h3 id="simple-version"&gt;&lt;a class="anchor" href="#simple-version"&gt;Simple version&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;First, a simplified variant which will make the more optimized variant much
more readily understood:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #215da8;"&gt;def &lt;/span&gt;&lt;span&gt;branchless_lomuto_partition_simplified(v, pred):
&lt;/span&gt;&lt;span&gt;    i = &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0  &lt;/span&gt;&lt;span style="color: #76647b;"&gt;# Loop invariant: all(pred(x) for x in v[:i])
&lt;/span&gt;&lt;span&gt;    j = &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0  &lt;/span&gt;&lt;span style="color: #76647b;"&gt;# Loop invariant: all(not pred(x) for x in v[i:j])
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;while &lt;/span&gt;&lt;span&gt;j &amp;lt; len(v):
&lt;/span&gt;&lt;span&gt;        v[i], v[j] = v[j], v[i]
&lt;/span&gt;&lt;span&gt;        i += int(pred(v[i]))
&lt;/span&gt;&lt;span&gt;        j += &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;return &lt;/span&gt;&lt;span&gt;i
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is actually quite similar to the original &lt;code&gt;lomuto_partition&lt;/code&gt;, except we
now always &lt;em&gt;unconditionally&lt;/em&gt; swap, and replace the conditional increment of $i$
with an &lt;code&gt;if&lt;/code&gt; statement by simply converting the boolean condition to an integer
and adding it to $i$.&lt;/p&gt;
&lt;p&gt;To visualize this, the state of the array looks like this after the unconditional
swap:&lt;/p&gt;
&lt;p&gt;&lt;img alt="A visual representation of the array post-swap." src="/blog/branchless-lomuto-partitioning/branchless-lomuto-post-swap.png" /&gt;&lt;/p&gt;
&lt;p&gt;From this it should be pretty clear that incrementing $i$ if the predicate
is true (corresponding to $v[i] &amp;lt; p$ for sorting) and unconditionally
incrementing $j$ restores our Lomuto loop invariant.
The only corner case is when ${i = j},$ but then the swap is a no-op and the
algorithm remains correct.&lt;/p&gt;
&lt;aside&gt;
&lt;p&gt;While researching prior art for this article
I came across &lt;a href="https://github.com/zeux/nanosort"&gt;nanosort&lt;/a&gt; by Arseny Kapoulkine
which implements this simplified variant and &lt;em&gt;thanks&lt;/em&gt; Andrei Alexandrescu for
his branchless Lomuto partition. But I actually believe it’s fundamentally
different to Andrei’s version.&lt;/p&gt;
&lt;/aside&gt;
&lt;h3 id="eliminating-swaps"&gt;&lt;a class="anchor" href="#eliminating-swaps"&gt;Eliminating swaps&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Swaps are equivalent to three moves, but by restructuring the algorithm we
can get away with two moves per iteration. The trick is to introduce a &lt;em&gt;gap&lt;/em&gt;
in the array by temporarily moving one of the elements out of the array.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #215da8;"&gt;def &lt;/span&gt;&lt;span&gt;branchless_lomuto_partition(v, pred):
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;if &lt;/span&gt;&lt;span&gt;len(v) == &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;return &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    tmp = v[&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;    i = &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0  &lt;/span&gt;&lt;span style="color: #76647b;"&gt;# Loop invariant: all(pred(x) for x in v[:i])
&lt;/span&gt;&lt;span&gt;    j = &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0  &lt;/span&gt;&lt;span style="color: #76647b;"&gt;# Loop invariant: all(not pred(x) for x in v[i:j])
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;while &lt;/span&gt;&lt;span&gt;j &amp;lt; len(v) - &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1&lt;/span&gt;&lt;span&gt;:
&lt;/span&gt;&lt;span&gt;        v[j] = v[i]
&lt;/span&gt;&lt;span&gt;        j += &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1
&lt;/span&gt;&lt;span&gt;        v[i] = v[j]
&lt;/span&gt;&lt;span&gt;        i += pred(v[i])
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    v[j] = v[i]
&lt;/span&gt;&lt;span&gt;    v[i] = tmp
&lt;/span&gt;&lt;span&gt;    i += pred(v[i])
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;return &lt;/span&gt;&lt;span&gt;i
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is our new branchless Lomuto partition algorithm. Its inner loop is
incredibly tight, involving only two moves, one predicate evaluation
and two additions. A full visualization of one iteration of the algorithm (the striped
red area indicates the gap in the array):&lt;/p&gt;
&lt;p&gt;&lt;img alt="A visualzation of one iteration of the algorithm." src="/blog/branchless-lomuto-partitioning/branchless-lomuto-iteration.png" /&gt;&lt;/p&gt;
&lt;p&gt;We can now compare &lt;a href="https://cpp.godbolt.org/z/zzzTh47PG"&gt;the assembly&lt;/a&gt; of the tight inner loops of Andrei Alexandrescu’s
branchless Lomuto partition and ours:&lt;/p&gt;
&lt;pre class="language-asm " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-asm"&gt;&lt;span&gt;.alexandrescu:
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;mov     &lt;/span&gt;&lt;span&gt;edi, DWORD PTR [rdx]
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;mov     &lt;/span&gt;&lt;span&gt;rsi, rdx
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;mov     &lt;/span&gt;&lt;span&gt;ebp, DWORD PTR [rax]
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;cmp     &lt;/span&gt;&lt;span&gt;edi, r8d
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;setb    &lt;/span&gt;&lt;span&gt;cl
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;sub     &lt;/span&gt;&lt;span&gt;rsi, rax
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;sar     &lt;/span&gt;&lt;span&gt;rsi, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;2
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;test    &lt;/span&gt;&lt;span&gt;cl, cl
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;cmove   &lt;/span&gt;&lt;span&gt;rsi, rbx
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;sal     &lt;/span&gt;&lt;span&gt;rcx, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;63
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;sar     &lt;/span&gt;&lt;span&gt;rcx, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;61
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;mov     &lt;/span&gt;&lt;span&gt;DWORD PTR [rax+rsi*&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;4&lt;/span&gt;&lt;span&gt;], ebp
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;lea     &lt;/span&gt;&lt;span&gt;r11, [&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0&lt;/span&gt;&lt;span&gt;+rsi*&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;4&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;mov     &lt;/span&gt;&lt;span&gt;rsi, rdx
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;add     &lt;/span&gt;&lt;span&gt;rdx, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;4
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;sub     &lt;/span&gt;&lt;span&gt;rsi, r11
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;sub     &lt;/span&gt;&lt;span&gt;rax, rcx
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;mov     &lt;/span&gt;&lt;span&gt;DWORD PTR [rsi], edi
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;cmp     &lt;/span&gt;&lt;span&gt;rdx, r10
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;jb      &lt;/span&gt;&lt;span&gt;.alexandrescu
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;.orlp:
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;lea     &lt;/span&gt;&lt;span&gt;rdi, [r8+rax*&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;4&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;mov     &lt;/span&gt;&lt;span&gt;ecx, DWORD PTR [rdi]
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;mov     &lt;/span&gt;&lt;span&gt;DWORD PTR [rdx], ecx
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;mov     &lt;/span&gt;&lt;span&gt;ecx, DWORD PTR [rdx+&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;4&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;cmp     &lt;/span&gt;&lt;span&gt;ecx, r9d
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;mov     &lt;/span&gt;&lt;span&gt;DWORD PTR [rdi], ecx
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;adc     &lt;/span&gt;&lt;span&gt;rax, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;add     &lt;/span&gt;&lt;span&gt;rdx, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;4
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;cmp     &lt;/span&gt;&lt;span&gt;rdx, r10
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;jne     &lt;/span&gt;&lt;span&gt;.orlp
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Half the instruction count! A neat trick the compiler did is translate the
addition of the boolean result of the predicate (which is just a comparison
here) to &lt;code&gt;adc rax, 0&lt;/code&gt;. It avoids needing to create a boolean 0/1 value in a
register by setting the carry flag using &lt;code&gt;cmp&lt;/code&gt; and adding zero with carry.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;&lt;a class="anchor" href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Is the new branchless Lomuto implementation worth it? For that I’ll hand you
over to my friend Lukas Bergdoll who has done an &lt;a href="https://github.com/Voultapher/sort-research-rs/blob/main/writeup/lomcyc_partition/text.md"&gt;extensive write-up&lt;/a&gt;
on the performance of an optimized implementation of this partition with a
variety of real-world benchmarks and metrics.&lt;/p&gt;
&lt;p&gt;From an algorithmic standpoint the branchless Lomuto- and Hoare-style algorithms
do have a key difference: they differ in the amount of writes they must perform.
Branchless Lomuto-style algorithms always do at least two moves for each
element, whereas Hoare-style algorithms can get away with doing a single move
for each element (crumsort), or even better, half a move per element on average
for random data (BlockQuicksort, pdqsort and bitsetsort, although they spend
more time figuring out what to move than crumsort does - one of many
trade-offs). So a key component of choosing an algorithm is the question “How
expensive are my moves?” which can vary from very cheap (small integers in
cache) to very expensive (large structs not in cache).&lt;/p&gt;
&lt;p&gt;Finally, the Lomuto-style algorithms tend to be significantly smaller in both
source code and generated machine code, which can be a factor for some. They
are also arguably easier to understand and prove correct, Hoare-style partition
algorithms are especially prone to off-by-one errors.&lt;/p&gt;</description><author>orlp.net - Blog Archive</author><pubDate>Mon, 04 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://orlp.net/blog/branchless-lomuto-partitioning/</guid></item><item><title>Profiling Rust programs the easy way</title><link>https://ntietz.com/blog/profiling-rust-programs-the-easy-way/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;Performance is one of the big reasons to use Rust.
It's not a magic wand for performance, it just gives you the control to eke out whatever performance you need.
So if your program is still slow, how do you fix that?&lt;/p&gt;
&lt;p&gt;Profiling your program is one of the best options for figuring out why it's slow and where you need to focus your improvement.
Without profiling, you're guessing blindly at where the problem may lie.
With a profile, you can see where most of the time is spent and focus your efforts.&lt;/p&gt;
&lt;p&gt;There are a few ways to profile Rust programs, but my favorite is &lt;a href="https://github.com/flamegraph-rs/flamegraph"&gt;&lt;code&gt;flamegraph&lt;/code&gt;&lt;/a&gt; (also called &lt;code&gt;cargo-flamegraph&lt;/code&gt;).
It's a wonderful tool that wraps around the standard profilers &lt;a href="https://en.wikipedia.org/wiki/Perf_(Linux)"&gt;perf&lt;/a&gt; (on Linux) and &lt;a href="https://en.wikipedia.org/wiki/DTrace"&gt;dtrace&lt;/a&gt; (on MacOS).&lt;/p&gt;
&lt;h1 id="basic-usage"&gt;Basic usage&lt;/h1&gt;
&lt;p&gt;The basic usage of &lt;code&gt;flamegraph&lt;/code&gt; is quite straightforward and their docs cover it well, but the amount of options can be daunting.
At its most basic, after you install it and dependencies&lt;sup class="footnote-reference" id="fr-1-1"&gt;&lt;a href="https://ntietz.com/blog/profiling-rust-programs-the-easy-way/#fn-1"&gt;[1]&lt;/a&gt;&lt;/sup&gt;, you can run it as a cargo command.
Here are a few of the invocations I use.&lt;/p&gt;
&lt;pre style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code&gt;&lt;span&gt;# Run your default target with no arguments
&lt;/span&gt;&lt;span&gt;cargo flamegraph
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;# Run your default target with the arguments after --
&lt;/span&gt;&lt;span&gt;cargo flamegraph -- arg1 arg2 arg3
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;# Run the specified bin target with arguments
&lt;/span&gt;&lt;span&gt;cargo flamegraph -b mybin -- arg1 arg2 arg3
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;# Run your default target with arguments and save
&lt;/span&gt;&lt;span&gt;# the results to a different filename
&lt;/span&gt;&lt;span&gt;cargo flamegraph -o myoutput.svg -- arg1 arg2 arg3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can mix and match these options to combine them.
Running one of these commands will produce a file, named &lt;code&gt;flamegraph.svg&lt;/code&gt; unless you overrode the output filename.&lt;/p&gt;
&lt;p&gt;After you generate that, you'll want to make sure the results are reasonable to get the results you need.
The output will tell you how much data it recorded and how many samples it took, something like this:&lt;/p&gt;
&lt;pre style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code&gt;&lt;span&gt;[ perf record: Woken up 59 times to write data ]
&lt;/span&gt;&lt;span&gt;[ perf record: Captured and wrote 14.706 MB perf.data (925 samples) ]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, we have 925 samples which is probably reasonable to make progress on the big things.
How many samples you need will vary depending on your program, but I've not found good results too far below 1,000, and far above that seems to make things really slow.
If you have big, sweeping inefficiencies, fewer samples will still catch them.
If they're relatively subtle and small gains, you may need many more samples.
It's an art to figure out how to tune the sample size.&lt;/p&gt;
&lt;p&gt;To control how many samples you get, you have two options: you can change your program, or change the instrumentation.
Sampling is done at a particular frequency, so you can control the program duration and you can control the sampling frequency.
If you're getting very few samples, but you can make your program run for longer (larger input, multiple repetitions, etc.) then that can increase the samples you get.
The same applies in reverse for too many samples.
The other option is to change the instrumentation itself: you can use the &lt;code&gt;-F&lt;/code&gt; argument to alter the frequency of sampling&lt;sup class="footnote-reference" id="fr-2-1"&gt;&lt;a href="https://ntietz.com/blog/profiling-rust-programs-the-easy-way/#fn-2"&gt;[2]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;pre style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code&gt;&lt;span&gt;# Sample at a rate of 1997 samples per second
&lt;/span&gt;&lt;span&gt;cargo flamegraph -F 1997 -- arg1 arg2 arg3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From here with a good sample, the work is now back to you, the programmer-analyst.
I like to open the SVG file in Firefox, which has a convenient viewer that allows you to zoom in and examine individual stacks of events.
But you can use any suitable SVG viewer.
You should be able to navigate around the &lt;code&gt;flamegraph&lt;/code&gt; to see visually where CPU time is being spent and use that to concentrate your efforts.
For a stronger introduction to how to read and use a flamegraph, see the &lt;a href="https://github.com/flamegraph-rs/flamegraph#systems-performance-work-guided-by-flamegraphs"&gt;flamegraph docs&lt;/a&gt; which has a section dedicated to this.&lt;/p&gt;
&lt;h1 id="a-few-gotchas"&gt;A few gotchas&lt;/h1&gt;
&lt;p&gt;While doing various profiling of my Rust programs, I've hit a few gotchas that tripped me up.
Here are the ones I remember.
There are certainly more, so &lt;a href="mailto:me@ntietz.com"&gt;let me know&lt;/a&gt; if there's something I should add to this list!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Missing system calls.&lt;/strong&gt; When the system under test spends a lot of time in system calls, those can lead to a misleading flamegraph if they aren't captured. Since system calls transfer control to the kernel, a standard user typically cannot measure them—and perf is by default running as you! To get around that, you can have it sample as root. In &lt;code&gt;flamegraph&lt;/code&gt; you would add the &lt;code&gt;--root&lt;/code&gt; flag, which will use sudo to get privileges to sample everything including during system calls. This is especially important when you're doing anything with a lot of disk or network activity, otherwise the code calling those system calls may be missing and you will be on a wild goose chase!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optimizations hiding information.&lt;/strong&gt; As stated in the &lt;a href="https://github.com/flamegraph-rs/flamegraph#improving-output-when-running-with---release"&gt;&lt;code&gt;flamegraph&lt;/code&gt; docs&lt;/a&gt;, "Due to optimizations etc... sometimes the quality of the information presented in the flamegraph will suffer when profiling release builds." To address this, you either set &lt;code&gt;debug = true&lt;/code&gt; for your release target, or you can use the environment variable &lt;code&gt;CARGO_PROFILE_RELEASE_DEBUG=true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lockstep sampling.&lt;/strong&gt; As Brendan Gregg &lt;a href="https://www.brendangregg.com/blog/2014-06-22/perf-cpu-sample.html"&gt;points out&lt;/a&gt;, sampling frequencies are set off from typical frequencies used by programs. If you use a frequency like 100Hz, you may end up on the same frequency of a repeating event in your program, resulting in sampling from the same point repeatedly instead of sampling from across the &lt;em&gt;entire&lt;/em&gt; program. You can experiment with different frequencies and see if any of them result in notably better or worse results; if they're all about the same, then you're probably not in lockstep with your program.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now go forth&lt;sup class="footnote-reference" id="fr-2-2"&gt;&lt;a href="https://ntietz.com/blog/profiling-rust-programs-the-easy-way/#fn-2"&gt;[2]&lt;/a&gt;&lt;/sup&gt; and profile your programs!&lt;/p&gt;
&lt;hr /&gt;
&lt;hr /&gt;&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-1"&gt;
&lt;p&gt;On Fedora, I had to install &lt;code&gt;perf&lt;/code&gt; with &lt;code&gt;sudo dnf install perf&lt;/code&gt;, and I had to &lt;em&gt;downgrade&lt;/em&gt; perf (&lt;code&gt;sudo dnf downgrade perf&lt;/code&gt;) since the latest version &lt;a href="https://github.com/flamegraph-rs/flamegraph/issues/280"&gt;has a regression&lt;/a&gt; which results in mangled names appearing in the generated results. If your results don't have the function names you expect, check for that. &lt;a href="https://ntietz.com/blog/profiling-rust-programs-the-easy-way/#fr-1-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-2"&gt;
&lt;p&gt;How many programming languages can you fit in a relatively normal sounding English sentence? Also, is there a language called "now" yet? &lt;a href="https://ntietz.com/blog/profiling-rust-programs-the-easy-way/#fr-2-1"&gt;↩&lt;/a&gt; &lt;a href="https://ntietz.com/blog/profiling-rust-programs-the-easy-way/#fr-2-2"&gt;↩2&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 04 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/profiling-rust-programs-the-easy-way/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Hikaru Nakamura's winning streaks</title><link>https://swaits.com/hikaru-winning-streaks/</link><description>&lt;p&gt;Chess legend &lt;a href="https://en.wikipedia.org/wiki/Vladimir_Kramnik" rel="external"&gt;Vladimir Kramnik&lt;/a&gt;
recently accused &lt;a href="https://en.wikipedia.org/wiki/Hikaru_Nakamura" rel="external"&gt;Hikaru
Nakamura&lt;/a&gt; of cheating. He called
out one of Hikaru's 46-game winning streaks as supporting evidence, claiming
such a streak was highly improbable.&lt;/p&gt;
&lt;p&gt;Quoting wikipedia:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In October and November, Nakamura participated in the &lt;a href="https://www.chess.com/events/2023-fide-grand-swiss/results" rel="external"&gt;FIDE Grand Swiss
2023&lt;/a&gt;, finishing
in second place with 8/11 points (+5-0=6) and thus qualifying for the
&lt;a href="https://www.fide.com/news/2138" rel="external"&gt;Candidates Tournament 2024&lt;/a&gt;. Vladimir
Kramnik made a statement on his Chess.com profile purportedly insinuating
that an unnamed high-level player was cheating on November 20. Nakamura
believed this post was targeted towards him, and responded with a statement
on
&lt;a href="https://indianexpress.com/article/sports/chess/he-doesnt-have-a-brain-hes-lost-it-hikaru-nakamuras-dig-at-vladimir-kramnik-over-garbage-accusations-of-cheating-9036793/" rel="external"&gt;Twitter&lt;/a&gt;
reading "Vladimir appears to be referencing my record...is he really accusing
me of cheating??? [sic]". Nakamura also expressed disappointment with &lt;a href="https://dotesports.com/chess/news/chess-pro-hikaru-hits-back-at-cheating-allegations" rel="external"&gt;Ian
Nepomniachtchi&lt;/a&gt;
for reposting Kramnik's claims.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The claim is dubious, at best. But I was curious enough to play with the data
myself.&lt;/p&gt;
&lt;!--toc:start--&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://swaits.com/hikaru-winning-streaks/#executive-summary"&gt;Executive Summary&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/hikaru-winning-streaks/#methodology"&gt;Methodology&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://swaits.com/hikaru-winning-streaks/#historical-data"&gt;Historical Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/hikaru-winning-streaks/#assumptions"&gt;Assumptions&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://swaits.com/hikaru-winning-streaks/#unwrap-is-ok"&gt;&lt;code&gt;unwrap()&lt;/code&gt; is ok&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/hikaru-winning-streaks/#definitions"&gt;Definitions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/hikaru-winning-streaks/#no-elo-updates"&gt;No Elo Updates&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/hikaru-winning-streaks/#draw-probability"&gt;Draw Probability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/hikaru-winning-streaks/#adjusted-win-probability"&gt;Adjusted Win Probability&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/hikaru-winning-streaks/#game-simulation"&gt;Game Simulation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/hikaru-winning-streaks/#results"&gt;Results&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://swaits.com/hikaru-winning-streaks/#actual-winning-streaks"&gt;Actual Winning Streaks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/hikaru-winning-streaks/#probability-of-win-streaks"&gt;Probability of Win Streaks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/hikaru-winning-streaks/#expected-number-of-win-streaks"&gt;Expected Number of Win Streaks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/hikaru-winning-streaks/#how-is-hikaru-performing"&gt;How is Hikaru Performing?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/hikaru-winning-streaks/#closing"&gt;Closing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;!--toc:end--&gt;
&lt;h2 id="executive-summary"&gt;Executive Summary&lt;/h2&gt;
&lt;p&gt;I started by downloading Hikaru's games from the
&lt;a href="https://chess.com/" rel="external"&gt;chess.com&lt;/a&gt; API. I wrote code in rust to load these
games and re-simulate his &lt;em&gt;chess.com career&lt;/em&gt; in order to arrive at
probabilities of achieving win streaks. Note that by &lt;em&gt;chess.com career&lt;/em&gt;, I mean
my analysis is limited to games which happened on chess.com only.&lt;/p&gt;
&lt;p&gt;Each game in the series was simulated based on the easily calculated
probability of win or loss between two opponents with &lt;a href="https://en.wikipedia.org/wiki/Elo_rating_system" rel="external"&gt;Elo
ratings&lt;/a&gt;. I simulated the
entire series of 47,407 games one million times. Yes, a million is overkill.
But it sounds cool.&lt;/p&gt;
&lt;p&gt;My analysis of 47,407 games played by Hikaru on chess.com shows that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the probability of hitting at least one 46 game winning streak is 100%, with
significant confidence&lt;/li&gt;
&lt;li&gt;the expected number of 46 game winning streaks is 2&lt;/li&gt;
&lt;li&gt;the actual number of 46 game winning streaks in Hikaru's chess.com career is 2&lt;/li&gt;
&lt;li&gt;the amount of BS in this cheating accusation is significantly high&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For graphs and some discussion, go straight to &lt;a href="https://swaits.com/hikaru-winning-streaks/#results"&gt;Results&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="methodology"&gt;Methodology&lt;/h2&gt;
&lt;p&gt;Hikaru's ability to win streaks completely depends on the sequence of opponents
and his skill difference with each opponent. Skill difference is, rather
conveniently, quantified in chess with Elo ratings. Given two Elo ratings, we
can determine the probability of one player beating another. Given the
historical data, which includes opponents and their Elo ratings, we can
re-simulate his career many many times and see how often he wins streaks. This
approach is a &lt;a href="https://en.wikipedia.org/wiki/Monte_Carlo_method" rel="external"&gt;Monte Carlo
method&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you want to see all the details of my model, browse the code &lt;a href="https://git.sr.ht/~swaits/hikaru" rel="external"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="historical-data"&gt;Historical Data&lt;/h3&gt;
&lt;p&gt;In order to start, I needed Hikaru's history from chess.com, sequenced in
order. Order is important because, as a popular chess streamer, Hikaru has
frequently played batches of opponents in very specific rating ranges,
sometimes significantly lower than his actual rating.&lt;/p&gt;
&lt;p&gt;To do this, I used &lt;code&gt;curl&lt;/code&gt; to hit the API directly, like this:&lt;/p&gt;
&lt;pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;# Fetch the list of game archive URLs for the player 'Hikaru' from Chess.com&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;archive_urls&lt;/span&gt;&lt;span style="color: #F29668;"&gt;=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt;curl&lt;/span&gt;&lt;span style="color: #95E6CB;"&gt; -Ls&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt; https://api.chess.com/pub/player/Hikaru/games/archives&lt;/span&gt;&lt;span style="color: #F29668;"&gt; |&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt; jq&lt;/span&gt;&lt;span style="color: #95E6CB;"&gt; -rc&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt; &amp;quot;.archives[]&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;# Iterate over each archive URL&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;for&lt;/span&gt;&lt;span&gt; url&lt;/span&gt;&lt;span style="color: #FF8F40;"&gt; in&lt;/span&gt;&lt;span&gt; $archive_urls&lt;/span&gt;&lt;span style="color: #BFBDB6B3;"&gt;;&lt;/span&gt;&lt;span style="color: #FF8F40;"&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;    # Fetch the games from each archive and extract the PGN&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #59C2FF;"&gt;    curl&lt;/span&gt;&lt;span style="color: #95E6CB;"&gt; -Ls&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$url&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #F29668;"&gt; |&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt; jq&lt;/span&gt;&lt;span style="color: #95E6CB;"&gt; -rc&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt; &amp;quot;.games[].pgn&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;done&lt;/span&gt;&lt;span style="color: #F29668;"&gt; &amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; games.pgn&lt;/span&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;  # Append all PGNs to the 'games.pgn' file&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I didn't figure this out on my own. Nope. Just found it on &lt;a href="https://www.reddit.com/r/chess/comments/8d4ou3/interest_in_downloading_all_chesscom_users_games/" rel="external"&gt;this reddit
post&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="assumptions"&gt;Assumptions&lt;/h3&gt;
&lt;h4 id="unwrap-is-ok"&gt;&lt;code&gt;unwrap()&lt;/code&gt; is ok&lt;/h4&gt;
&lt;p&gt;I'm using rust to code this simulation. We don't need full blown error handling
here. This is just a hacky experiment. So, I'm going to use &lt;code&gt;unwrap()&lt;/code&gt; wherever
I want and be perfectly okay with it. To the coders out there - this isn't how
I'd write production code!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Had to say it before my fellow rustaceans jump on me!&lt;/em&gt;&lt;/p&gt;
&lt;h4 id="definitions"&gt;Definitions&lt;/h4&gt;
&lt;p&gt;A &lt;em&gt;streak&lt;/em&gt; of 5 wins is also two streaks of 4 wins. So, if you win games &lt;code&gt;1 2 3 4 5&lt;/code&gt;, it also implies you won &lt;code&gt;1 2 3 4&lt;/code&gt; and &lt;code&gt;2 3 4 5&lt;/code&gt;. For the purpose of this
analysis, a streak only refers to the longest streak. Sub-streaks, as I've
shown here, are &lt;strong&gt;not counted&lt;/strong&gt; as streaks.&lt;/p&gt;
&lt;h4 id="no-elo-updates"&gt;No Elo Updates&lt;/h4&gt;
&lt;p&gt;The best simulation we could do of Hikaru's chess.com career includes not only
using Elo ratings to simulate game outcomes, but then using those simulated
outcomes to update his (and his opponent's) ratings.&lt;/p&gt;
&lt;p&gt;However, I decided against doing this because I assume (fairly confidently)
that many of the pairings in his sequence of games were based on his and his
opponent's ratings. Therefore, we simply take the recorded ratings and assume
that the game outcomes are based on them.&lt;/p&gt;
&lt;p&gt;This means that if we simulate an unlikely loss, we'd expect Hikaru's rating to
go down. And therefore, his next opponent might be different. But, since we
don't have access to the pairing methodology used in each game, we must assume
that the next opponent is the same.&lt;/p&gt;
&lt;h4 id="draw-probability"&gt;Draw Probability&lt;/h4&gt;
&lt;p&gt;Given two Elo ratings, we can deterministically find the probability of white
or black winning. However, it doesn't tell us anything about draws. In order to
simulate draws, we need to use a heuristic assumption. I came up with this:&lt;/p&gt;
&lt;figure&gt;
$$ P(\text{draw}) = \frac{K_{\text{max}}}{1 + K_{\text{strength}} \times
  |R_{\text{black}} - R_{\text{white}}|} $$
&lt;/figure&gt;
&lt;p&gt;Where \( K_{\text{max}} \) is the maximum probability of a draw and \(
K_{\text{strength}} \) determines how the function scales according to the
rating difference.&lt;/p&gt;
&lt;p&gt;In my case, I landed on  \( K_{\text{max}} = 0.18 \) and \(
K_{\text{strength}} = 0.05 \). I admit, I just eyeballed this. I found that
about 18% of Hikaru's games were draws. And, I looked at how quickly the
probability of a draw goes to zero as the rating difference increases. Feels
good enough to me. But, I welcome input on how it can be better.&lt;/p&gt;
&lt;p&gt;The resulting rust code looks like:&lt;/p&gt;
&lt;pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;// estimate probability of draw given two Elo ratings&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;fn&lt;/span&gt;&lt;span style="color: #FFB454;"&gt; probability_of_draw&lt;/span&gt;&lt;span&gt;(white_elo&lt;/span&gt;&lt;span style="color: #F29668;"&gt;: &amp;amp;&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt;Rating&lt;/span&gt;&lt;span&gt;, black_elo&lt;/span&gt;&lt;span style="color: #F29668;"&gt;: &amp;amp;&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt;Rating&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span style="color: #F29668;"&gt; -&amp;gt;&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt; f64&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;    // Constants for draw probability estimation&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;    let&lt;/span&gt;&lt;span&gt; max_draw_probability&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt; 0&lt;/span&gt;&lt;span style="color: #BFBDB6B3;"&gt;.&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt;18&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt; // Upper limit of draw probability&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;    let&lt;/span&gt;&lt;span&gt; rating_difference_sensitivity&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt; 0&lt;/span&gt;&lt;span style="color: #BFBDB6B3;"&gt;.&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt;05&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt; // Adjusts sensitivity to rating difference&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;    let&lt;/span&gt;&lt;span&gt; rating_diff&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span&gt; (black_elo&lt;/span&gt;&lt;span style="color: #F29668;"&gt;.&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt;0&lt;/span&gt;&lt;span style="color: #FF8F40;"&gt; as&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt; f64&lt;/span&gt;&lt;span style="color: #F29668;"&gt; -&lt;/span&gt;&lt;span&gt; white_elo&lt;/span&gt;&lt;span style="color: #F29668;"&gt;.&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt;0&lt;/span&gt;&lt;span style="color: #FF8F40;"&gt; as&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt; f64&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span style="color: #F29668;"&gt;.&lt;/span&gt;&lt;span style="color: #FFB454;"&gt;abs&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    max_draw_probability&lt;/span&gt;&lt;span style="color: #F29668;"&gt; /&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt;1&lt;/span&gt;&lt;span style="color: #BFBDB6B3;"&gt;.&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt;0&lt;/span&gt;&lt;span style="color: #F29668;"&gt; +&lt;/span&gt;&lt;span&gt; rating_difference_sensitivity&lt;/span&gt;&lt;span style="color: #F29668;"&gt; *&lt;/span&gt;&lt;span&gt; rating_diff)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="adjusted-win-probability"&gt;Adjusted Win Probability&lt;/h4&gt;
&lt;p&gt;Now that we know the draw probability, we can adjust the win probability. This
is done by subtracting 1.0 from the draw probability to arriave at a total
(black or white) win probability, and then using the Elo expectation equation
to assign that remaining win probability proportionately.&lt;/p&gt;
&lt;p&gt;First, find the probability of any player winning:&lt;/p&gt;

$$ P(\text{white or black})  = 1.0 - P(\text{draw})$$

&lt;p&gt;Next, compute the expectation of winning for each player according to their Elo
ratings:&lt;/p&gt;

$$ P(\text{white}) = \frac{1}{1 + 10^{(R_{\text{black}} - R_{\text{white}}) / 400}} $$
$$ P(\text{black}) = 1 - P(\text{white}) $$

&lt;p&gt;And finally, adjust for the overall win probability, which takes draws into
account:&lt;/p&gt;

$$ P(\text{white}) = P(\text{white}) \times P(\text{white or black}) $$
$$ P(\text{black}) = P(\text{black}) \times P(\text{white or black}) $$

&lt;p&gt;In code, it looks like this:&lt;/p&gt;
&lt;pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;// estimate probability of white winning, given two Elo ratings&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;fn&lt;/span&gt;&lt;span style="color: #FFB454;"&gt; probability_of_white_win&lt;/span&gt;&lt;span&gt;(white_elo&lt;/span&gt;&lt;span style="color: #F29668;"&gt;: &amp;amp;&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt;Rating&lt;/span&gt;&lt;span&gt;, black_elo&lt;/span&gt;&lt;span style="color: #F29668;"&gt;: &amp;amp;&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt;Rating&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span style="color: #F29668;"&gt; -&amp;gt;&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt; f64&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;    // Expected outcomes for players A and B&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;    // E_a = 1 / (1 + 10^((b - a) / 400)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;    // E_b = 1 - E_a&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #D2A6FF;"&gt;    1&lt;/span&gt;&lt;span style="color: #BFBDB6B3;"&gt;.&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt;0&lt;/span&gt;&lt;span style="color: #F29668;"&gt; /&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt;1&lt;/span&gt;&lt;span style="color: #BFBDB6B3;"&gt;.&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt;0&lt;/span&gt;&lt;span style="color: #F29668;"&gt; +&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt; 10&lt;/span&gt;&lt;span style="color: #BFBDB6B3;"&gt;.&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt;0&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt;f64&lt;/span&gt;&lt;span style="color: #F29668;"&gt;.&lt;/span&gt;&lt;span style="color: #FFB454;"&gt;powf&lt;/span&gt;&lt;span&gt;((black_elo&lt;/span&gt;&lt;span style="color: #F29668;"&gt;.&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt;0&lt;/span&gt;&lt;span style="color: #FF8F40;"&gt; as&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt; f64&lt;/span&gt;&lt;span style="color: #F29668;"&gt; -&lt;/span&gt;&lt;span&gt; white_elo&lt;/span&gt;&lt;span style="color: #F29668;"&gt;.&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt;0&lt;/span&gt;&lt;span style="color: #FF8F40;"&gt; as&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt; f64&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span style="color: #F29668;"&gt; /&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt; 400&lt;/span&gt;&lt;span style="color: #BFBDB6B3;"&gt;.&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt;0&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;// estimate probability of black winning given white's probability of winning&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;fn&lt;/span&gt;&lt;span style="color: #FFB454;"&gt; probability_of_black_win&lt;/span&gt;&lt;span&gt;(probability_of_white_win&lt;/span&gt;&lt;span style="color: #F29668;"&gt;:&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt; f64&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span style="color: #F29668;"&gt; -&amp;gt;&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt; f64&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #D2A6FF;"&gt;    1&lt;/span&gt;&lt;span style="color: #BFBDB6B3;"&gt;.&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt;0&lt;/span&gt;&lt;span style="color: #F29668;"&gt; -&lt;/span&gt;&lt;span&gt; probability_of_white_win&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;// given two ratings, compute probabilities of win/loss/draw and return in `ExpectedOutcome`&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;pub fn&lt;/span&gt;&lt;span style="color: #FFB454;"&gt; calculate_expected_outcome&lt;/span&gt;&lt;span&gt;(white_elo&lt;/span&gt;&lt;span style="color: #F29668;"&gt;: &amp;amp;&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt;Rating&lt;/span&gt;&lt;span&gt;, black_elo&lt;/span&gt;&lt;span style="color: #F29668;"&gt;: &amp;amp;&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt;Rating&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span style="color: #F29668;"&gt; -&amp;gt;&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt; ExpectedOutcome&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;    // Estimate win probabilities&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;    let mut&lt;/span&gt;&lt;span&gt; p_white&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #FFB454;"&gt; probability_of_white_win&lt;/span&gt;&lt;span&gt;(white_elo, black_elo);&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;    let mut&lt;/span&gt;&lt;span&gt; p_black&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #FFB454;"&gt; probability_of_black_win&lt;/span&gt;&lt;span&gt;(p_white);&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;    // Estimate draw probability based on rating difference&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;    let&lt;/span&gt;&lt;span&gt; p_draw&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #FFB454;"&gt; probability_of_draw&lt;/span&gt;&lt;span&gt;(white_elo, black_elo);&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;    // Adjust win probabilities to account for draw probability&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    p_white&lt;/span&gt;&lt;span style="color: #F29668;"&gt; *=&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt; 1&lt;/span&gt;&lt;span style="color: #BFBDB6B3;"&gt;.&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt;0&lt;/span&gt;&lt;span style="color: #F29668;"&gt; -&lt;/span&gt;&lt;span&gt; p_draw;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    p_black&lt;/span&gt;&lt;span style="color: #F29668;"&gt; *=&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt; 1&lt;/span&gt;&lt;span style="color: #BFBDB6B3;"&gt;.&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt;0&lt;/span&gt;&lt;span style="color: #F29668;"&gt; -&lt;/span&gt;&lt;span&gt; p_draw;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;    // Sanity check&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FFB454;"&gt;    assert_approx_eq!&lt;/span&gt;&lt;span&gt;((p_white&lt;/span&gt;&lt;span style="color: #F29668;"&gt; +&lt;/span&gt;&lt;span&gt; p_black&lt;/span&gt;&lt;span style="color: #F29668;"&gt; +&lt;/span&gt;&lt;span&gt; p_draw),&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt; 1&lt;/span&gt;&lt;span style="color: #BFBDB6B3;"&gt;.&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #59C2FF;"&gt;    ExpectedOutcome&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;        // Estimate win probabilities&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;        white&lt;/span&gt;&lt;span style="color: #F29668;"&gt;:&lt;/span&gt;&lt;span&gt; p_white,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;        black&lt;/span&gt;&lt;span style="color: #F29668;"&gt;:&lt;/span&gt;&lt;span&gt; p_black,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;        draw&lt;/span&gt;&lt;span style="color: #F29668;"&gt;:&lt;/span&gt;&lt;span&gt; p_draw,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="game-simulation"&gt;Game Simulation&lt;/h3&gt;
&lt;p&gt;We simulate a game by using the two players' Elo ratings to compute teh win
probability of each player and the draw probability, which always sums to 1.0.
Then we simply use a random number to choose a simulated outcome. Here's the
rust code:&lt;/p&gt;
&lt;pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;// simulates a single game&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;fn&lt;/span&gt;&lt;span style="color: #FFB454;"&gt; simulate_game&lt;/span&gt;&lt;span&gt;(white_elo&lt;/span&gt;&lt;span style="color: #F29668;"&gt;: &amp;amp;&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt;Rating&lt;/span&gt;&lt;span&gt;, black_elo&lt;/span&gt;&lt;span style="color: #F29668;"&gt;: &amp;amp;&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt;Rating&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span style="color: #F29668;"&gt; -&amp;gt;&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt; Outcome&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;    // Get expected outcomes&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;    let&lt;/span&gt;&lt;span&gt; e&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #FFB454;"&gt; calculate_expected_outcome&lt;/span&gt;&lt;span&gt;(white_elo, black_elo);&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;    // Roll the dice&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;    let mut&lt;/span&gt;&lt;span&gt; rng&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt; rand&lt;/span&gt;&lt;span style="color: #F29668;"&gt;::&lt;/span&gt;&lt;span style="color: #FFB454;"&gt;thread_rng&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;    let&lt;/span&gt;&lt;span&gt; random_value&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span&gt; rng&lt;/span&gt;&lt;span style="color: #F29668;"&gt;.&lt;/span&gt;&lt;span style="color: #FFB454;"&gt;gen&lt;/span&gt;&lt;span style="color: #F29668;"&gt;::&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt;f64&lt;/span&gt;&lt;span&gt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;    // Determine and return outcome&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;    if&lt;/span&gt;&lt;span&gt; random_value&lt;/span&gt;&lt;span style="color: #F29668;"&gt; &amp;lt;=&lt;/span&gt;&lt;span&gt; e&lt;/span&gt;&lt;span style="color: #F29668;"&gt;.&lt;/span&gt;&lt;span&gt;white {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #59C2FF;"&gt;        Outcome&lt;/span&gt;&lt;span style="color: #F29668;"&gt;::&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt;WhiteWin&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    }&lt;/span&gt;&lt;span style="color: #FF8F40;"&gt; else if&lt;/span&gt;&lt;span&gt; random_value&lt;/span&gt;&lt;span style="color: #F29668;"&gt; &amp;lt;=&lt;/span&gt;&lt;span&gt; (e&lt;/span&gt;&lt;span style="color: #F29668;"&gt;.&lt;/span&gt;&lt;span&gt;white &lt;/span&gt;&lt;span style="color: #F29668;"&gt;+&lt;/span&gt;&lt;span&gt; e&lt;/span&gt;&lt;span style="color: #F29668;"&gt;.&lt;/span&gt;&lt;span&gt;black) {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #59C2FF;"&gt;        Outcome&lt;/span&gt;&lt;span style="color: #F29668;"&gt;::&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt;BlackWin&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    }&lt;/span&gt;&lt;span style="color: #FF8F40;"&gt; else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #59C2FF;"&gt;        Outcome&lt;/span&gt;&lt;span style="color: #F29668;"&gt;::&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt;Draw&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now to simulate the entire series of Hikaru's games, we just apply this
function to the historical games to come up with a new set of outcomes.&lt;/p&gt;
&lt;p&gt;Like this:&lt;/p&gt;
&lt;pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;// simulate a series of games and return a new simulated `Vec&amp;lt;Game&amp;gt;` series&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;pub fn&lt;/span&gt;&lt;span style="color: #FFB454;"&gt; simulate_games&lt;/span&gt;&lt;span&gt;(actual&lt;/span&gt;&lt;span style="color: #F29668;"&gt;: &amp;amp;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt;Game&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;span style="color: #F29668;"&gt; -&amp;gt;&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt; Vec&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt;Game&lt;/span&gt;&lt;span&gt;&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    actual&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #F29668;"&gt;        .&lt;/span&gt;&lt;span style="color: #FFB454;"&gt;iter&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #F29668;"&gt;        .&lt;/span&gt;&lt;span style="color: #FFB454;"&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #F29668;"&gt;|&lt;/span&gt;&lt;span&gt;g&lt;/span&gt;&lt;span style="color: #F29668;"&gt;|&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt; Game&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;            white&lt;/span&gt;&lt;span style="color: #F29668;"&gt;:&lt;/span&gt;&lt;span&gt; g&lt;/span&gt;&lt;span style="color: #F29668;"&gt;.&lt;/span&gt;&lt;span&gt;white&lt;/span&gt;&lt;span style="color: #F29668;"&gt;.&lt;/span&gt;&lt;span style="color: #FFB454;"&gt;to_owned&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;            black&lt;/span&gt;&lt;span style="color: #F29668;"&gt;:&lt;/span&gt;&lt;span&gt; g&lt;/span&gt;&lt;span style="color: #F29668;"&gt;.&lt;/span&gt;&lt;span&gt;black&lt;/span&gt;&lt;span style="color: #F29668;"&gt;.&lt;/span&gt;&lt;span style="color: #FFB454;"&gt;to_owned&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;            outcome&lt;/span&gt;&lt;span style="color: #F29668;"&gt;:&lt;/span&gt;&lt;span style="color: #FFB454;"&gt; simulate_game&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #F29668;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;g&lt;/span&gt;&lt;span style="color: #F29668;"&gt;.&lt;/span&gt;&lt;span&gt;white&lt;/span&gt;&lt;span style="color: #F29668;"&gt;.&lt;/span&gt;&lt;span&gt;rating,&lt;/span&gt;&lt;span style="color: #F29668;"&gt; &amp;amp;&lt;/span&gt;&lt;span&gt;g&lt;/span&gt;&lt;span style="color: #F29668;"&gt;.&lt;/span&gt;&lt;span&gt;black&lt;/span&gt;&lt;span style="color: #F29668;"&gt;.&lt;/span&gt;&lt;span&gt;rating),&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;        })&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #F29668;"&gt;        .&lt;/span&gt;&lt;span style="color: #FFB454;"&gt;collect&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once we have all the (one million!) simulated chess.com careers, we build
histograms of achieved streaks for each and then merge them into a single
histogram for the entire simulation. Here it is all together:&lt;/p&gt;
&lt;pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;let&lt;/span&gt;&lt;span&gt; iterations&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt; 1_000_000&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;let&lt;/span&gt;&lt;span&gt; histograms&lt;/span&gt;&lt;span style="color: #F29668;"&gt;:&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt; Vec&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt;HashMap&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt;usize&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #59C2FF;"&gt; usize&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt;0&lt;/span&gt;&lt;span style="color: #F29668;"&gt;..&lt;/span&gt;&lt;span&gt;iterations)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #F29668;"&gt;    .&lt;/span&gt;&lt;span style="color: #FFB454;"&gt;into_par_iter&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #F29668;"&gt;    .&lt;/span&gt;&lt;span style="color: #FFB454;"&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #F29668;"&gt;|&lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span style="color: #F29668;"&gt;|&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;        let&lt;/span&gt;&lt;span&gt; simulated_games&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #FFB454;"&gt; simulate_games&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #F29668;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;games);&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FFB454;"&gt;        generate_win_streak_histogram&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;Hikaru&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #F29668;"&gt; &amp;amp;&lt;/span&gt;&lt;span&gt;simulated_games)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #F29668;"&gt;    .&lt;/span&gt;&lt;span style="color: #FFB454;"&gt;collect&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;// Reduce the histograms into a single histogram&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;let&lt;/span&gt;&lt;span&gt; merged_histogram&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span&gt; histograms&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #F29668;"&gt;    .&lt;/span&gt;&lt;span style="color: #FFB454;"&gt;into_iter&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #F29668;"&gt;    .&lt;/span&gt;&lt;span style="color: #FFB454;"&gt;reduce&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #F29668;"&gt;|&lt;/span&gt;&lt;span style="color: #FF8F40;"&gt;mut&lt;/span&gt;&lt;span&gt; acc, h&lt;/span&gt;&lt;span style="color: #F29668;"&gt;|&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;        for&lt;/span&gt;&lt;span&gt; (k, v)&lt;/span&gt;&lt;span style="color: #FF8F40;"&gt; in&lt;/span&gt;&lt;span&gt; h {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #F29668;"&gt;            *&lt;/span&gt;&lt;span&gt;acc&lt;/span&gt;&lt;span style="color: #F29668;"&gt;.&lt;/span&gt;&lt;span style="color: #FFB454;"&gt;entry&lt;/span&gt;&lt;span&gt;(k)&lt;/span&gt;&lt;span style="color: #F29668;"&gt;.&lt;/span&gt;&lt;span style="color: #FFB454;"&gt;or_insert&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span style="color: #F29668;"&gt; +=&lt;/span&gt;&lt;span&gt; v;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;        acc&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #F29668;"&gt;    .&lt;/span&gt;&lt;span style="color: #FFB454;"&gt;unwrap&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note the use of &lt;code&gt;rayon&lt;/code&gt; with &lt;code&gt;into_par_iter()&lt;/code&gt;. This is a really nice bit of
code which makes it trivially easy to use all my cores.&lt;/p&gt;
&lt;p&gt;You can find all of my code &lt;a href="https://git.sr.ht/~swaits/hikaru" rel="external"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="results"&gt;Results&lt;/h2&gt;
&lt;p&gt;I simulated the entire 47,407 game sequence one million times. This section
details my findings.&lt;/p&gt;
&lt;h3 id="actual-winning-streaks"&gt;Actual Winning Streaks&lt;/h3&gt;
&lt;p&gt;Before getting into the model's results, I think it's interesting to look at
what we found in Hikaru's data.&lt;/p&gt;
&lt;p&gt;My code reports a good number of long winning streaks in Hikaru's chess.com
career. Here are his 40-game and longer streaks, along with the number of times
he achieved each:&lt;/p&gt;
&lt;pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;Streak Length:  40, Count: 4&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Streak Length:  41, Count: 2&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Streak Length:  42, Count: 4&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Streak Length:  43, Count: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Streak Length:  44, Count: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Streak Length:  45, Count: 2&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Streak Length:  46, Count: 2  &amp;lt;= OMG DID HE CHEAT WTF 🤡&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Streak Length:  47, Count: 6&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Streak Length:  48, Count: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Streak Length:  50, Count: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Streak Length:  51, Count: 2&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Streak Length:  52, Count: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Streak Length:  54, Count: 3&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Streak Length:  55, Count: 2&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Streak Length:  56, Count: 2&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Streak Length:  57, Count: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Streak Length:  58, Count: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Streak Length:  59, Count: 2&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Streak Length:  61, Count: 2&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Streak Length:  62, Count: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Streak Length:  69, Count: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Streak Length:  72, Count: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Streak Length:  77, Count: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Streak Length:  78, Count: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Streak Length:  82, Count: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Streak Length:  85, Count: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Streak Length: 100, Count: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Streak Length: 117, Count: 1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yes, you're seeing that right. There are 85, 100, and even 117 winning streaks.
You see, these streamers will sometimes play a lower-rated player many times
consecutively. If they prevail some pre-chosen number of times (say, 100), then
they will colloquially say that they "adopted" that player. It's fun. And this
is why it's important to look at the entire, exact sequence of opponents Hikaru
played.&lt;/p&gt;
&lt;h3 id="probability-of-win-streaks"&gt;Probability of Win Streaks&lt;/h3&gt;
&lt;p&gt;Based on my analysis, the following chart shows the probability of getting at
least one winning streak. For example, if you look at 46 on the x-axis, you'll
see its probability is 100% on the y-axis.&lt;/p&gt;
&lt;p&gt;As another example, you can see that the probability of hitting at least one
80-game winning streak is just a bit below 30%.&lt;/p&gt;
&lt;p&gt;We can even see about a 5% change Hikaru would've hit a 140 game streak. So, if
there were 20 parallel Hikaru's playing exactly the same games, maybe one of
them would have pulled this off.&lt;/p&gt;
&lt;p&gt;&lt;img alt="probability chart" src="https://swaits.com/hikaru-winning-streaks/probability.png" /&gt;&lt;/p&gt;
&lt;p&gt;Oh, but what about those weird bumps at the 70, 100, 115, and even 140 streaks?
Again, remember that this is based entirely on the sequence of opponents
Hikaru played. If at some point in his nearly 50k games he played someone rated
significantly lower than him for 70, 100, 115, or 140 times consecutively, then
that increases the probability of winning that streak.&lt;/p&gt;
&lt;p&gt;And when we look at the data, Hikaru does in fact have streaks at 69, 72, 77,
78, 82, 85, 100, and 117.&lt;/p&gt;
&lt;p&gt;Herein lies the plainly obvious reasoning that Kramnik seems to be missing.&lt;/p&gt;
&lt;h3 id="expected-number-of-win-streaks"&gt;Expected Number of Win Streaks&lt;/h3&gt;
&lt;p&gt;Probability is one thing. In this case, it removes all doubt about the
possibility of achieving something like a 46-game winning streak.&lt;/p&gt;
&lt;p&gt;But what's more interesting when we look at whether someone cheated is how
close to the expectations they performed. The chart below plots both the
expected number of each winning streak and actual number of winning streaks
recorded in Hikaru's career.&lt;/p&gt;
&lt;p&gt;The red line is the number of streaks predicted by my modeling. The blue line
is the number of streaks Hikaru scored.&lt;/p&gt;
&lt;p&gt;For example, we can see that we expect Hikaru to get about 35 streaks of 20
wins. Just look across the x-axis to 20, and go up to find that the red line
crosses about about 35. But note that the blue line shows Hikaru only actually
got about 28 of those 20 game streaks.&lt;/p&gt;
&lt;p&gt;If we look at the 46 streak data point, we see an expectation of 2. And we also
see an actual of 2. Hmmmmm....&lt;/p&gt;
&lt;p&gt;One note on this chart: the y-axis is logarithmic. This makes it so that we can
show a much larger range of data. It might seem a little weird to some, but
it's pretty normal.&lt;/p&gt;
&lt;p&gt;&lt;img alt="expected and actual streaks" src="https://swaits.com/hikaru-winning-streaks/expected.png" /&gt;&lt;/p&gt;
&lt;p&gt;The data gets a bit noisy to the right of the 25 streak line. This is because
such streaks occur less frequently, and thus our sample size gets quite a bit
smaller.&lt;/p&gt;
&lt;h3 id="how-is-hikaru-performing"&gt;How is Hikaru Performing?&lt;/h3&gt;
&lt;p&gt;Now, let's plot the difference between what Hikaru did and what my simulation
expects at each of the streaks of 20 and higher.&lt;/p&gt;
&lt;p&gt;To use this chart, look for a winning streak on the x-axis, like 55. We see
that the (actual - expected) is 1. This means that Hikaru recorded 1 more
55-game winning streak than expected.&lt;/p&gt;
&lt;p&gt;&lt;img alt="difference of expected and actual streaks" src="https://swaits.com/hikaru-winning-streaks/delta.png" /&gt;&lt;/p&gt;
&lt;p&gt;Generally, we can see that for streaks 20 and above, Hikaru is performing just
about as expected. If we smoothed this line to account for the noisiness at
this end of the data, we'd find that he's well within the margin of
expectations.&lt;/p&gt;
&lt;h2 id="closing"&gt;Closing&lt;/h2&gt;
&lt;p&gt;Hikaru's win streaks on chess.com are normal. As I said in the beginning, my
analysis of 47,407 games played by Hikaru on chess.com shows that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the probability of hitting at least one 46 game winning streak is 100%, with
significant confidence&lt;/li&gt;
&lt;li&gt;the expected number of 46 game winning streaks is 2&lt;/li&gt;
&lt;li&gt;the actual number of 46 game winning streaks in Hikaru's chess.com career is 2&lt;/li&gt;
&lt;li&gt;the amount of bullshit in this cheating accusation is significantly high&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Being great at one thing, chess in this case, doesn't mean you're great at
everything. Even being literally one of the best chess players in the world is
not a reason to think someone is &lt;em&gt;smart&lt;/em&gt;. But, our society tells people who
achieve greatness in chess that they're smart. And apparently for some folks,
especially in this case, that's gone to their head. Now, perhaps Kramnik has
some other evidence to Hikaru's cheating. But, what we've seen thus far - the 46
game winning streaks - is not enough. It's not enough for even the most basic of
high-school level math and statistics. And so, being chess world champion does
not mean you're smart.&lt;/p&gt;
&lt;p&gt;That said, many chess players are actually damn smart. It tends to attract the
bookish type who are into educating themselves. And, nothing but respect from me
for people who can play chess. I am not one of those people.&lt;/p&gt;
&lt;p&gt;If you find a mistake in my work that shows I'm wrong, please let me know!&lt;/p&gt;
&lt;p&gt;And finally, I'll leave you with this, courtesy of ChatGPT and DALL-E:&lt;/p&gt;
&lt;p&gt;&lt;img alt="a clown playing chess" src="https://swaits.com/hikaru-winning-streaks/clown.jpeg" /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A portrait of a sad clown sitting at a chess table, deeply engrossed in a
game of chess. The clown has a striking resemblance to a tall, slender man
with sharp facial features, reminiscent of a famous chess grandmaster. He
wears a traditional clown outfit with a ruffled collar and a small hat, and
his face is painted with exaggerated sad expressions. The setting is a dimly
lit room, with a focus on the chessboard and the contemplative pose of the
clown.&lt;/p&gt;
&lt;/blockquote&gt;</description><author>swaits.com</author><pubDate>Mon, 04 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://swaits.com/hikaru-winning-streaks/</guid></item><item><title>AWS re:invent 2023</title><link>https://tig.pt/aws-re-invent-2023/</link><description>&lt;p&gt;This was my first re:invent and boy, oh boy, it was overwhelming in a good way. I want more!&lt;/p&gt;&lt;p&gt;It was my first time outside of Europe, and I have to say, jetlag is a real thing, with 8 hours difference I was waking up at 3 am every&lt;/p&gt;</description><author>Tiago Rodrigues</author><pubDate>Sun, 03 Dec 2023 21:52:00 GMT</pubDate><guid isPermaLink="true">https://tig.pt/aws-re-invent-2023/</guid></item><item><title>Ordering a Cargo field</title><link>https://river.me/blog/cargo-order/</link><description>How can you enforce a sorting order on a Cargo field when displaying tabular data?</description><author>River Writes - A MediaWiki Blog</author><pubDate>Sun, 03 Dec 2023 20:02:20 GMT</pubDate><guid isPermaLink="true">https://river.me/blog/cargo-order/</guid></item><item><title>ChatGPT After One Year</title><link>https://mattrickard.com/chatgpt-after-one-year</link><description>ChatGPT was released on November 30th 2022. What has changed since then?

- **Hundreds of open-source models.** Varying sized models from small to very large. M</description><author>Matt Rickard</author><pubDate>Sun, 03 Dec 2023 16:30:00 GMT</pubDate><guid isPermaLink="true">https://mattrickard.com/chatgpt-after-one-year</guid></item><item><title>Alternatives to (math) academia and how to get there</title><link>https://www.georgeyw.com/math-alternatives/</link><description>This is a reference post for math PhDs</description><author>This Is My True Name</author><pubDate>Sun, 03 Dec 2023 09:08:55 GMT</pubDate><guid isPermaLink="true">https://www.georgeyw.com/math-alternatives/</guid></item><item><title>Comic: Magic spells</title><link>https://zackproser.com/blog/magic-spells</link><description>A comic strip about casting magic spells</description><author>Zachary Proser</author><pubDate>Sun, 03 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/magic-spells</guid></item><item><title>You Buy; They Fly</title><link>https://taylor.town/you-buy-they-fly</link><description>Friends are cool. Spending money sucks; travel is exhausting.</description><author>taylor.town</author><pubDate>Sun, 03 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/you-buy-they-fly</guid></item><item><title>2023 in Review</title><link>https://jasoneckert.github.io/myblog/2023-in-review/</link><description>&lt;p&gt;For me, 2023 turned out to be a year of learning, growth, adventure, and gratitude. And since there&amp;rsquo;s plenty for me to talk about, I&amp;rsquo;ve divided my review into two sections: one that summarizes my professional pursuits, and one that summarizes everything else.&lt;/p&gt;
&lt;h1 id="the-professional-stuff"&gt;The professional stuff:&lt;/h1&gt;
&lt;p&gt;On the college front, the new &lt;a href="https://www.trios.com/programs-courses/technology/"&gt;technology programs&lt;/a&gt; I&amp;rsquo;ve been pushing since 2020 finally got to market! My favourite one is &lt;em&gt;&lt;strong&gt;Mobile Web Developer&lt;/strong&gt;&lt;/em&gt;, as it&amp;rsquo;s a radical departure from what we did previously. The whole program is taught exclusively on macOS, has two interlaced capstones, and is carefully crafted to allow students to build solid developer skills using modern industry tooling and processes with zero tests/quizzes (students instead perform weekly assignments). These weekly assignments allow instructors to assess and guide skills development for individual students throughout each course, as well as echo the sprint cadence used within industry that students must become accustomed to. We also have a new &lt;em&gt;&lt;strong&gt;IT Professional (Infrastructure, Cloud &amp;amp; Cybersecurity)&lt;/strong&gt;&lt;/em&gt; program that restructures and augments many of the topics in our previous IT Professional program to allow for expansion into the areas of cloud and cybersecurity (both of which are key industry focal points today).&lt;/p&gt;</description><author>Jason Eckert's Website and Blog</author><pubDate>Sun, 03 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jasoneckert.github.io/myblog/2023-in-review/</guid></item><item><title>Konrad Zuse's Early Computers (Review)</title><link>https://ztoz.blog/posts/zuse-early-computers/</link><description>&lt;p&gt;Although historians are usually pushed to decide questions of &amp;ldquo;who was first,&amp;rdquo; the question of &lt;em&gt;impact&lt;/em&gt; is more interesting. Konrad Zuse has a claim to the first computer, the first high-level computer language, and the first European commercial computer sale, but his relative isolation working in Germany in the 30s and 40s limited his visibility and impact. It wasn&amp;rsquo;t until the 1970s for his innovations to become more widely known. Raúl Rojas has long contributed to the historical appreciation of Zuse and the remarkable modern nature of his machines.  This volume collects and makes available in English many of his previous articles on the subject.&lt;/p&gt;
  &lt;img alt="Book cover" class="fit-image" src="https://ztoz.blog/posts/zuse-early-computers/978-3-031-39876-6.webp" /&gt;
  

&lt;blockquote&gt;
&lt;p&gt;Raúl Rojas. &lt;em&gt;Konrad Zuse&amp;rsquo;s Early Computers: The Quest for the Computer in Germany&lt;/em&gt;. Springer 2023. &lt;a href="https://doi.org/10.1007/978-3-031-39876-6"&gt;Publisher Link&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The book is primarily a technical history, discussing how each of Zuse&amp;rsquo;s early computers (Z1, Z2, Z3, and Z4) were designed and implemented, including the post-war effort to add conditional jumps to the Z4. The author also provides one of the clearest explanations of Plankalkül, Zuse&amp;rsquo;s high-level, symbolic programming language with an unusual two-dimensional syntax (likely inspired by engineering spreadsheet computation forms). The author also covers the S1 and S2, special-purpose computing machines for finding control corrections caused by aircraft wing manufacturing defects. Zuse&amp;rsquo;s efforts towards chess playing software are explained. Finally, the book contains two chapters providing historical context on the world-wide efforts to build computers and insight into the politics of Zuse and his close associates.&lt;/p&gt;
&lt;p&gt;Rojas' explanations are very clear, improved by a large number of illustrations and diagrams. The tone is measured and he provides evidence for his claims, and the limits of those claims. For instance, the Z4, built between 1942 and 1945, lacked any instructions for jumps, conditional or not. Zuse was aware of the limitation (as noted in his journals of the time), but since the Z4 only had 64 words of memory, he felt it was an acceptable limitation. After the war, the ETH university leased the machine in 1950, but with the proviso that Zuse add conditional jumping. Using only microinstructions, he was able to add test operations (e.g. x equals zero, x is greater than zero) and a subroutine jump instruction. Although the jump was still limited, limiting programs to a single loop, this was sufficient for the use cases of the day. Rojas discusses how this limitation factors into the debate over the first computer, both practically, comparatively (comparing it to the ENIAC and the IBM/Harvard Mark I), and theoretically (how this limitation can be ignored in terms of Turing&amp;rsquo;s infinite tape).&lt;/p&gt;
&lt;p&gt;(If you are interested in how the Z4 was used at ETH, I recommend reading the chapter &amp;ldquo;Computer Development in Switzerland&amp;rdquo; in Herbert Bruderer&amp;rsquo;s &lt;em&gt;Milestones in Analog and Digital Computing&lt;/em&gt;, Vol 2.)&lt;/p&gt;
&lt;p&gt;In terms of the book&amp;rsquo;s goal &amp;mdash; describe the progression of Zuse&amp;rsquo;s thinking and the development of his computers through the specification of the Plankalkül language &amp;mdash; it succeeds in its purpose. We learn little about Zuse himself, but that is not the point of the book. Due to the after-effects of the war, Zuse was penniless and lost many years before he could restart his efforts and build new computers. In the mean-time, the U.S. and British efforts quickly iterated on new designs and established new markets. While Zuse was not unknown &amp;mdash; he met Turing and Plankalkül was referenced by the ALGOL committee &amp;mdash; his impact was limited, perhaps partially due to his patents being disregarded by the German government and his doctorate thesis not being published.&lt;/p&gt;
&lt;p&gt;However, since the matter of impact is all post-war, I think the book ended too early. Zuse&amp;rsquo;s story is intertwined with post-war politics and geography, the influence of patents, and the beginning of the commercial computer industry. None of these are technical topics or within the author&amp;rsquo;s expression of scope, but by not including them, the reader needs to read another volume to get the full story.&lt;/p&gt;
&lt;p&gt;Also, the book could be improved by the addition of some color plates as some of the photos and diagrams have more detail than is comfortable in grey-scale. Most of the diagrams are high-quality and easy to read.&lt;/p&gt;
&lt;p&gt;Recommended. At $38 for the hardbound edition, this is a good value for an academic work.&lt;/p&gt;</description><author>ℤ→ℤ</author><pubDate>Sun, 03 Dec 2023 01:39:27 GMT</pubDate><guid isPermaLink="true">https://ztoz.blog/posts/zuse-early-computers/</guid></item><item><title>An Album for Each Year (2023)</title><link>https://williamhuster.com/an-album-for-each-year-2023/</link><description>&lt;p&gt;I love lists. So here’s a list albums, one for each year of my life. This exercise was inspired by &lt;a href="https://www.allthingsdistributed.com/2008/07/an_album_for_each_year.html"&gt;a blog post by the CTO of Amazon, Dr. Werner Vogels&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The rules are simple, Select “a favorite album for every year of your life”. The challenge had two restrictions:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Only one album per year&lt;/li&gt;
  &lt;li&gt;No repeat artists&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I’m probably profiling myself pretty heavily by sharing this list, but who cares! I won’t go through them all, but I have a meaningful memory attached to each of these albums. My 2023 album of choice is a particularly weird and delightful one: “One Wayne G” by Mac Demarco. This album is actually an archive of musical sketches and experiments. The songs are not really complete or cohesive, but they have Mac’s signature style and are all pleasant to listen to. The titles of the songs are mostly just dates, like “20221102.” I found this really inspirational. I ended up doing a few similar musical experiments this year because of this album. Titling song sketches with a date is liberating, because it relieves the pressure of trying to &lt;em&gt;make a complete song&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;One of my favorite songs on the album actually does have a title: &lt;em&gt;Proud, True Toyota&lt;/em&gt;. It’s hilarious and speaks to me, because I too own a proud, true, Toyota Tundra. The song is ridiculous, and it’s also ridiculous that I live in Washington DC and own an old pickup truck! It’s honestly not very smart or convenient, but it makes me happy.&lt;/p&gt;

&lt;p&gt;Finally, the album will also always reminds me of my mom who passed away this year. I was playing the album through my bluetooth speakers at home with her in May, around Mother’s day. That was just a few weeks before she died on June 7.&lt;/p&gt;

&lt;p&gt;1987: The Cure, &lt;em&gt;Kiss Me, Kiss Me, Kiss Me&lt;/em&gt;&lt;br /&gt;
1988: The Pixies, &lt;em&gt;Surfer Rosa&lt;/em&gt;&lt;br /&gt;
1989: Kate Bush, &lt;em&gt;The Sensual World&lt;/em&gt;&lt;br /&gt;
1990: Depeche Mode, &lt;em&gt;Violator&lt;/em&gt;&lt;br /&gt;
1991: Nirvana, &lt;em&gt;Nevermind&lt;/em&gt;&lt;br /&gt;
1992: Weird Al Yankovic, &lt;em&gt;Off the Deep End&lt;/em&gt;&lt;br /&gt;
1993: Smashing Pumpkins, &lt;em&gt;Siamese Dream&lt;/em&gt;&lt;br /&gt;
1994: Pink Floyd, &lt;em&gt;Division Bell&lt;/em&gt;&lt;br /&gt;
1995: Björk, &lt;em&gt;Post&lt;/em&gt;&lt;br /&gt;
1996: Barenaked Ladies, &lt;em&gt;Born on a Pirate Ship&lt;/em&gt;&lt;br /&gt;
1997: Radiohead, &lt;em&gt;OK Computer&lt;/em&gt;&lt;br /&gt;
1998: Neutral Milk Hotel, &lt;em&gt;In the Aeroplane Over the Sea&lt;/em&gt;&lt;br /&gt;
1999: Eiffel 65, &lt;em&gt;Europop&lt;/em&gt;&lt;br /&gt;
2000: Enya, &lt;em&gt;A Day Without Rain&lt;/em&gt;&lt;br /&gt;
2001: The Strokes, &lt;em&gt;Is This It&lt;/em&gt;&lt;br /&gt;
2002: Coldplay, &lt;em&gt;Clocks&lt;/em&gt;&lt;br /&gt;
2003: Belle and Sebastian, &lt;em&gt;Dear Catastrophe Waitress&lt;/em&gt;&lt;br /&gt;
2004: The Killers, &lt;em&gt;Hot Fuss&lt;/em&gt;&lt;br /&gt;
2005: Modest Mouse, &lt;em&gt;Good News for People Who Love Bad News&lt;/em&gt;&lt;br /&gt;
2006: Hot Chip, &lt;em&gt;The Warning&lt;/em&gt;&lt;br /&gt;
2007: Bright Eyes, &lt;em&gt;Cassadaga&lt;/em&gt;&lt;br /&gt;
2008: Bon Iver, &lt;em&gt;For Emma, Forever Ago&lt;/em&gt;&lt;br /&gt;
2009: Grizzly Bear, &lt;em&gt;Veckatimest&lt;/em&gt;&lt;br /&gt;
2010: Kanye West, &lt;em&gt;My Beautiful Dark Twisted Fantasy&lt;/em&gt;&lt;br /&gt;
2011: Sufjan Stevens, &lt;em&gt;The Age of Adz&lt;/em&gt;&lt;br /&gt;
2012: Grimes, &lt;em&gt;Visions&lt;/em&gt;&lt;br /&gt;
2013: Chvrches, &lt;em&gt;The Bones of What You Believe&lt;/em&gt;&lt;br /&gt;
2014: St Vincent, &lt;em&gt;St Vincent&lt;/em&gt;&lt;br /&gt;
2015: Tame Impala, &lt;em&gt;Currents&lt;/em&gt;&lt;br /&gt;
2016: David Bowie, &lt;em&gt;Blackstar&lt;/em&gt;&lt;br /&gt;
2017: Kendrick Lamar, &lt;em&gt;DAMN.&lt;/em&gt;&lt;br /&gt;
2018: Janelle Monáe, &lt;em&gt;Dirty Computer&lt;/em&gt;&lt;br /&gt;
2019: Aldous Harding, &lt;em&gt;Designer&lt;/em&gt;&lt;br /&gt;
2020: Phoebe Bridgers, &lt;em&gt;Punisher&lt;/em&gt;&lt;br /&gt;
2021: Khruangbin, &lt;em&gt;Mordechai&lt;/em&gt;&lt;br /&gt;
2022: Mighty Brother, &lt;em&gt;Azimuth&lt;/em&gt;&lt;br /&gt;
2023: Mac Demarco, &lt;em&gt;One Wayne G&lt;/em&gt;&lt;/p&gt;</description><author>William Huster</author><pubDate>Sat, 02 Dec 2023 20:00:00 GMT</pubDate><guid isPermaLink="true">https://williamhuster.com/an-album-for-each-year-2023/</guid></item><item><title>McNamara Fallacy</title><link>https://mattrickard.com/mcnamara-fallacy</link><description>The McNamara Fallacy is named after Robert McNamara, the US Secretary of Defense during the Vietnam War. The fallacy describes making decisions using only quant</description><author>Matt Rickard</author><pubDate>Sat, 02 Dec 2023 16:30:00 GMT</pubDate><guid isPermaLink="true">https://mattrickard.com/mcnamara-fallacy</guid></item><item><title>Assumptions can be dangerous</title><link>https://ilearnt.com/blog/backupwoes/</link><description>&lt;p&gt;There is a quote &amp;ldquo;A backup is not a backup unless you have recently performed a restore from it&amp;rdquo;; I got to find this out the hard way.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Sat, 02 Dec 2023 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/backupwoes/</guid></item><item><title>Python Coroutines</title><link>https://www.danielcorin.com/til/python/coroutines/</link><description>Exploring accidental synchronous blocking in asynchronous programming in Python with coroutines.</description><author>Thought Eddies</author><pubDate>Sat, 02 Dec 2023 11:44:55 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/python/coroutines/</guid></item><item><title>Goodbye Twitter</title><link>https://blog.reiterate.app/misc/2023/12/01/goodbye-twitter/</link><description>I’ve removed all Twitter links from my site.</description><author>Reiterations</author><pubDate>Sat, 02 Dec 2023 03:52:00 GMT</pubDate><guid isPermaLink="true">https://blog.reiterate.app/misc/2023/12/01/goodbye-twitter/</guid></item><item><title>For All Mankind: Season 1</title><link>https://olshansky.info/tv/for_all_mankind_season_1/</link><description>Olshansky's review of For All Mankind: Season 1</description><author>🦉 olshansky 🦁</author><pubDate>Sat, 02 Dec 2023 03:18:01 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/tv/for_all_mankind_season_1/</guid></item><item><title>Retrospective on November 2023 wellness challenge</title><link>https://benovermyer.com/blog/2023/12/retrospective-on-november-2023-wellness-challenge/</link><description>&lt;p&gt;November's wellness challenge was a success.&lt;/p&gt;
&lt;p&gt;I had no alcohol, despite going out with friends and coworkers multiple
times. It helps that there are several good non-alcoholic beer options these days.
I also kept up the workout regimen. I went to the gym four times a week every
week. The only exception was Thanksgiving Day.&lt;/p&gt;
&lt;p&gt;Speaking of Thanksgiving, despite eating normal Thanksgiving dinner (twice), I
only had a brief bump in weight gain. I'm now the lowest weight I've been since
I started tracking in 2019.&lt;/p&gt;
&lt;p&gt;I'm planning on continuing the workout regimen and keeping my diet dialed in for
muscle gain and fat loss. This morning I weighed in at 219 lbs. I'm hoping to get
down to 200 lbs. by mid February, but a more realistic goal is 210 lbs. We'll see
where I end up.&lt;/p&gt;
&lt;p&gt;I never tried gauging what my one-rep bench press is. I should probably try that
next time I'm at the gym.&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Sat, 02 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2023/12/retrospective-on-november-2023-wellness-challenge/</guid></item><item><title>Advent of Code 2023 in Oracle - Day 1 and 2</title><link>https://hartenfeller.dev/blog/advent-of-code-oracle-2023-day1-2</link><description>Advent of Code 2023 in Oracle SQL and PL/SQL. Day 1 and 2.</description><author>Philipp Hartenfeller Blog RSS Feed</author><pubDate>Sat, 02 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://hartenfeller.dev/blog/advent-of-code-oracle-2023-day1-2</guid></item><item><title>Vector search and Retrieval Augmented Generation</title><link>https://michael-lewis.com/posts/vector-search-and-retrieval-augmented-generation/</link><description>&lt;p&gt;This is an overview of vector search and Retrieval Augmented Generation (RAG) in the context of text data, illustrated with worked code examples, and some closing thoughts on their usage in practice.&lt;/p&gt;
&lt;p&gt;It is primarily to clarify my understanding and for my future reference, and is likely to be updated with further information over time. It may be of interest to others, but those familiar with vector search and RAG can skip the terminology and examples sections.&lt;/p&gt;</description><author>Michael Ian Lewis</author><pubDate>Sat, 02 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://michael-lewis.com/posts/vector-search-and-retrieval-augmented-generation/</guid></item><item><title>Advent of Code 2023</title><link>https://mfashby.net/posts/2023-12-01-aoc2023/</link><description/><author>Home on mfashby.net</author><pubDate>Fri, 01 Dec 2023 23:42:21 GMT</pubDate><guid isPermaLink="true">https://mfashby.net/posts/2023-12-01-aoc2023/</guid></item><item><title>Render Python Encodings Error</title><link>https://www.danielcorin.com/til/render/python-encodings/</link><description>Render Python Encodings Error</description><author>Thought Eddies</author><pubDate>Fri, 01 Dec 2023 20:04:04 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/render/python-encodings/</guid></item><item><title>tstate - strongly typed Typescript state machines</title><link>https://blog.martijnarts.com/tstate-strongly-typed-typescript-state-machines/</link><description>&lt;p&gt;If you read &lt;a href="https://blog.martijnarts.com/predictable-programming-3-using-xstate/"&gt;this&lt;/a&gt; &lt;a href="https://blog.martijnarts.com/testing-complex-xstate-machines/"&gt;blog&lt;/a&gt; or &lt;a href="https://martijnarts.com/atoms/State-machines-in-frontend-dev"&gt;my digital garden&lt;/a&gt; you&amp;apos;ll know I like working with state machines in the frontend. There&amp;apos;s plenty of reasons for this, but the two main ones are that user interaction maps really well to state machines, and so do shared background&lt;/p&gt;</description><author>Martijn Arts</author><pubDate>Fri, 01 Dec 2023 19:12:26 GMT</pubDate><guid isPermaLink="true">https://blog.martijnarts.com/tstate-strongly-typed-typescript-state-machines/</guid></item><item><title>Data Quality in LLMs</title><link>https://mattrickard.com/data-quality-in-llms</link><description>Good data is the difference between Mistral’s LLMs and Llama, which share similar architectures but different datasets.

To train LLMs, you need data that is:</description><author>Matt Rickard</author><pubDate>Fri, 01 Dec 2023 16:30:00 GMT</pubDate><guid isPermaLink="true">https://mattrickard.com/data-quality-in-llms</guid></item><item><title>Document your house</title><link>https://ilearnt.com/blog/housedocument/</link><description>&lt;p&gt;We have manuals and guides for the software we use. We have a manual for the car we drive. Why not the house?&lt;/p&gt;</description><author>I Learnt</author><pubDate>Fri, 01 Dec 2023 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/housedocument/</guid></item><item><title>Doctor Who: The Unearthly Child (1963) Audio Description Script</title><link>https://brett.coulstock.id.au/doctor-who-the-unearthly-child-pilot-1963-audio-description.html</link><description>Unofficial audio-description script for the unbroadcast pilot episode of the 1963 Doctor Who serial The Unearthly Child in CSV, HTML and SRT format.</description><author>Brett Coulstock - Writer</author><pubDate>Fri, 01 Dec 2023 11:00:00 GMT</pubDate><guid isPermaLink="true">https://brett.coulstock.id.au/doctor-who-the-unearthly-child-pilot-1963-audio-description.html</guid></item><item><title>Control - how to make a game enjoyable for casual audiences</title><link>https://ounapuu.ee/posts/2023/12/01/control/</link><description>How one toggle in the game settings allowed me to enjoy a notoriously difficult game.</description><author>./techtipsy</author><pubDate>Fri, 01 Dec 2023 10:00:00 GMT</pubDate><guid isPermaLink="true">https://ounapuu.ee/posts/2023/12/01/control/</guid></item><item><title>Ideas for filtering fediverse timelines</title><link>https://seirdy.one/notes/2023/11/30/ideas-for-filtering-fediverse-timelines/</link><description>&lt;p&gt;An alternative to controversial recommendation algorithms is timeline-filtering algorithms. Feeling pressured to scroll through too much is unhealthy and lends itself to addiction; filtering things down once a user scrolls down far enough should be a welcome &lt;strong&gt;opt-in&lt;/strong&gt; feature.&lt;/p&gt;&lt;p&gt;For example, a mutuals-only filter combined with a minimum-interaction-count filter could be a good way to “catch up” quickly after a long absence. Only showing posts common across a set of timelines is another; for example, I would like to see a filtered view of Akkoma’s bubble timeline restricted to what’s on my home timeline.&lt;/p&gt;&lt;p&gt;Conditionally showing reblogs is another way to filter. Unread reblogs from mutuals should be the only visible reblogs I have scrolled down far enough.&lt;/p&gt;</description><author>All content on Seirdy’s Home</author><pubDate>Fri, 01 Dec 2023 09:12:05 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/notes/2023/11/30/ideas-for-filtering-fediverse-timelines/</guid></item><item><title>App Defaults in 2023</title><link>https://www.spencerharston.com/posts/2023/app-defaults-in-2023/</link><description>&lt;p&gt;Inspired by others I've seen across various blogs and on Mastodon, here's a list of default apps I've been using at the end of 2023. I doubt this will change much year-to-year, but we'll see what next year brings.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Email&lt;/strong&gt;: Gmail&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Calendar&lt;/strong&gt;: Google Calendar&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documents/Cloud Storage&lt;/strong&gt;: Google Drive&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PDF Reader&lt;/strong&gt;: SumatraPDF&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Photos&lt;/strong&gt;: Google Photos&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Password Manager&lt;/strong&gt;: Bitwarden&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Notes/Lists/Tasks&lt;/strong&gt;: Google Keep, Tasks.org (Android)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Terminal&lt;/strong&gt;: Windows Terminal, Termux (Android)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Text Editor&lt;/strong&gt;: Visual Studio Code&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Screenshots&lt;/strong&gt;: ShareX&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Browser (Desktop)&lt;/strong&gt;: Firefox Developer Edition, Chrome (Google apps only)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Browser (Mobile)&lt;/strong&gt;: Fennec, DuckDuckGo, Cromite, Chrome (Google apps only)
&lt;ul&gt;
&lt;li&gt;I use &lt;a href="https://github.com/TrianguloY/UrlChecker" rel="noreferrer" target="_blank"&gt;URLCheck&lt;/a&gt; to pick which app to use when tapping a link&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Search Engine&lt;/strong&gt;: DuckDuckGo&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Chat&lt;/strong&gt;: Google Messages, Discord, GroupMe&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Music&lt;/strong&gt;: YouTube Music, MusicBee, Symfonium (Android)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Podcasts&lt;/strong&gt;: Pocketcasts (Android)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audiobooks&lt;/strong&gt;: Audiobookshelf&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ebooks&lt;/strong&gt;: Calibre&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RSS Reader&lt;/strong&gt;: FreshRSS&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mastodon&lt;/strong&gt;: Moshidon (Android)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hacker News&lt;/strong&gt;: Harmonic (Android)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Twitch&lt;/strong&gt;: Xtra (Android)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Habits&lt;/strong&gt;: Loop Habit Tracker (Android)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;File Browser&lt;/strong&gt;: Solid Explorer (Android)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ad Blocker&lt;/strong&gt;: uBlock Origin, NextDNS&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;VPN&lt;/strong&gt;: Mullvad, Google One, Tailscale&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See also my &lt;a href="https://www.spencerharston.com/uses" rel="noreferrer" target="_blank"&gt;Uses&lt;/a&gt; page for hardware and other software.&lt;/p&gt;</description><author>Spencer Harston</author><pubDate>Fri, 01 Dec 2023 07:21:00 GMT</pubDate><guid isPermaLink="true">https://www.spencerharston.com/posts/2023/app-defaults-in-2023/</guid></item><item><title>Startup Reflections</title><link>https://lukesingham.com/startup-reflections/</link><description>&lt;p&gt;These are some of the tough lessons learned from my start-up journey. Writing this has been incredibly cathartic.&lt;/p&gt;
&lt;p&gt;My top two key-takeaways are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Validate the problem and willingness to pay upfront i.e. find someone that says 'how can I use this now? Please take my money'&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Stress test your co-founder relationship.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id="product"&gt;Product &lt;a class="direct-link" href="https://lukesingham.com/startup-reflections/#product"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;For a little context, our two sentence description of ilumadata was:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;ilumadata is Retool for data pipelines. We're building a cloud editor for enterprise data teams that makes it easy to build, test, and launch data transformations in seconds.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Lessons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Read &lt;a href="https://www.goodreads.com/book/show/52283963-the-mom-test?ac=1&amp;amp;from_search=true&amp;amp;qid=hsNb4HuTjb&amp;amp;rank=1"&gt;the Mom test&lt;/a&gt; first, it's a quick read and will save you a lot of time. To validate your product, you need to:
&lt;ul&gt;
&lt;li&gt;Find and validate the problems your target market has. Ask, &lt;em&gt;what is your biggest problem? Have you tried fixing it? How often does this happen? What is the impact?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Get some sort of commitment. &lt;em&gt;Will they sign a Letter of Interest (LOI)? Introduce you to their boss? Pay money now?&lt;/em&gt; Yes you can do this without a product.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;For ilumadata the adoption cost was greater than the value add. See the &lt;a href="http://www.startupengine.org/2013/08/the-collision-installation.html"&gt;Collison install&lt;/a&gt; for how Stripe reduced their initial adoption cost.&lt;/li&gt;
&lt;li&gt;The difference between iterating on your product based on discovery calls and no users vs. &lt;em&gt;paying&lt;/em&gt; users is huge.&lt;/li&gt;
&lt;li&gt;Ultimately ilumadata was a &lt;em&gt;nice to have&lt;/em&gt; - a proliferation of data tools, and a change in the macroeconomy meant no space for nice to haves.&lt;/li&gt;
&lt;li&gt;Having domain expertise can de-risk the venture. A lot of seed funding is based on a solid team with a domain insight. On reflection, was our insight shallow? Maybe, there are a million reasons for why startups fail. The big product de-risk is asking, do people want this? No, do they &lt;em&gt;really&lt;/em&gt; want it? I &lt;em&gt;thought&lt;/em&gt; I de-risked this early on with some discovery calls, but in retrospect they failed the &lt;a href="https://www.goodreads.com/book/show/52283963-the-mom-test?ac=1&amp;amp;from_search=true&amp;amp;qid=hsNb4HuTjb&amp;amp;rank=1"&gt;Mom test&lt;/a&gt;. To de-risk efficiently, try selling a product that doesn't exist.&lt;/li&gt;
&lt;li&gt;Iterate faster - constantly update what you are trying to learn from the next call. What is the hypothesis you are trying to prove/disprove?&lt;/li&gt;
&lt;li&gt;There isn't a definitive way to know when to pivot. There are plenty of examples where founders stuck to their initial idea and it worked out e.g. Retool and Airbnb. In our case we did not have enough traction to justify sticking to our initial idea.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;if a company makes a product that people dislike yet still buy, then presumably they'd buy a better alternative if you made one&amp;quot; - &lt;a href="http://paulgraham.com/superlinear.html"&gt;pg&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;👆 When we started, no one liked dbt cloud, the mistake was thinking they were an indicator of success. Yet a year later, based on our user interviews dbt cloud did not appear to have high uptake of their paid cloud product. 7 months into our venture they had laid off 50% of their workforce and doubled their pricing. dbt core solves some real pain points, but as far as I can tell, dbt cloud is a nice to have. dbt has recently overhauled their cloud product, so perhaps they have found a way to make it a must have - time will tell 🤷‍♀️&lt;/li&gt;
&lt;li&gt;Does your product need SOC II? We thought we did. We had found an evangelist user at a large US healthcare company who was keen to use ilumadata. Maybe it's going to be our first enterprise sale! In prepping us for the sales process they said we needed SOC II. So we rushed out and got it in two weeks. The lead went cold, completely ghosting us 👻. But we'd just forked out 20k to get our shiny SOC II badge. In hindsight, before getting SOC II, we should have been in front of the &lt;em&gt;buyer&lt;/em&gt; and got a commitment from them. SOC II companies sell early stage startups on fear 'Don't let SOC II be the reason you lose your first big deal'.  If you are really solving a problem, you should get a commitment from the &lt;em&gt;buyer&lt;/em&gt; before you get SOC II.&lt;/li&gt;
&lt;li&gt;We were building a data platform, a data operating system, a &lt;em&gt;single pane of glass&lt;/em&gt; for data infrastructure. Rather than tackle one single easy to communicate problem, we were solving many smaller problems. We'd tell ourselves certain features were table stakes. For early stage, I'd avoid being a platform. It's hard to communicate, hard to sell and hard to build.&lt;/li&gt;
&lt;li&gt;Start quantifying the top problems from each discovery call. &lt;a href="https://maze.co/guides/card-sorting/"&gt;Card sorting&lt;/a&gt; is a great exercise to do this. Put 5-7 problems you think your persona has on a kanban board, share your screen and ask them to sort them from most painful to least. Several months in, we realised that we weren't addressing the most significant or painful problem. Card sorting also gets people talking about their problems.&lt;/li&gt;
&lt;li&gt;In an attempt to breach the chasm between the data team and the rest of the business, we fell into it. The product was too technical still for business folk and too light touch for those tech inclined data people.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="co-founders"&gt;Co-Founders &lt;a class="direct-link" href="https://lukesingham.com/startup-reflections/#co-founders"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;Here's a guide on: &lt;a href="https://lukesingham.com/how-to-find-a-co-founder/"&gt;How to find a co-founder&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Co-founding a company is the business version of marriage. The first signs we didn't work well together occurred during the YC batch. We had left the happy place of coding as a side project, and now were making all sorts of time pressured decisions to make this a successful company. Our communication styles were very different. Founders are literally the foundation of the company, not working well together always meant a separation was inevitable&lt;sup class="footnote-ref"&gt;&lt;a href="https://lukesingham.com/startup-reflections/#fn1" id="fnref1"&gt;[1]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Lessons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Perhaps the most important factor in an early stage startup, &lt;em&gt;really&lt;/em&gt; invest time here building and testing the relationship.&lt;/li&gt;
&lt;li&gt;Make sure you go through and write down your answers to &lt;a href="https://proof-assets.s3.amazonaws.com/firstround/50%20Questions%20for%20Co-Founders.pdf"&gt;First Mark's 50 Founder Questions&lt;/a&gt;. It prompts both you and your potential co-founder to reflect on some hard questions, but also helps uncover where there are discrepancies between the two of you. Do this before you start working together. We didn't do this.&lt;/li&gt;
&lt;li&gt;It is a marriage, your incorporation documents are your marriage certificate and it takes significant legal fees, stress and heartache to get divorced. Would you get married to someone you haven't really spent time getting to know?
&lt;ul&gt;
&lt;li&gt;Work together - starting to code on the idea is unlikely a good stress test. It's just one area of what you need to work on as founders and as tech co-founders you're in your happy place! It tests whether you work as an engineering duo. But what about when you're stressed, tired, and have to make a decision on product direction? I know founders who have purposely set up 12hr days working side by side with a packed agenda to stress test. Others have gone on a two week working 'holiday' together - and then chosen not to continue, a lot better than years later.&lt;/li&gt;
&lt;li&gt;Pick things you disagree on and commit to debating passionately. If you can't do this productively now, how will you in the future?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Listen to any negative hunches you might have - this will likely get worse later on. Would you hang out solo with them, just for fun? If not, why not?&lt;/li&gt;
&lt;li&gt;Pre-define areas of ownership. I made the mistake of everyone does everything, which is often true in startups, but everyone doesn't have to &lt;em&gt;agree&lt;/em&gt; on everything. Seeking consensus on operational items should perhaps be open to a round or two of feedback, but ultimately the decision to execute and move fast lies with the owner. Your co-founder should be comfortable with this. Areas of responsibility and how you resolve deadlocks is covered in the &lt;a href="https://proof-assets.s3.amazonaws.com/firstround/50%20Questions%20for%20Co-Founders.pdf"&gt;50 Founder Questions.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="sales"&gt;Sales &lt;a class="direct-link" href="https://lukesingham.com/startup-reflections/#sales"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;We initially had an enterprise sales motion. Following several months of zero traction, we shifted our focus down market, targeting small to medium enterprises. Take the following lessons with a grain of salt, because ultimately we didn't make lots of sales! 😞&lt;/p&gt;
&lt;p&gt;Lessons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Enterprise sales is hard. Super long sales cycle. Lots of touch points. I've had a few emails an entire year later saying they are now 'interested' in buying.&lt;/li&gt;
&lt;li&gt;Pre-Product Market Fit both founders should be doing at least some selling. Stick your noses into the market. If you're not selling, you're not learning. And if you haven't found a 'hair on fire' problem, then building may be a waste of time. But sure does feel productive!&lt;/li&gt;
&lt;li&gt;Customer onboarding - ensure technical founder/s are present for this. Feel the customer onboarding pain, so it gets prioritised appropriately against other engineering pains.&lt;/li&gt;
&lt;li&gt;Read the Mom test first, oh I said this already! Have you read it yet?&lt;/li&gt;
&lt;li&gt;You can sell first, then build. Yes, sell a product that doesn't exist. Do outbound &amp;quot;we're building X that solves Y&amp;quot; - this is your hypothesis that you should be able to accept or reject after 5 decent calls. Focus on the person's problems and if what you're building solves a genuine pain point, be transparent about the development progress and get a commitment e.g. a meeting with their boss or an LOI or a design partnership!&lt;/li&gt;
&lt;li&gt;There were a couple of hunches I felt when selling to a technical audience. Firstly, they were more interested in &lt;em&gt;how&lt;/em&gt; you engineered something - were they wondering how to build it themselves? Secondly, technical teams trust open source more. Which is related to the ease of adoption.&lt;/li&gt;
&lt;li&gt;Depending on your product, the user and buyer may be different people. This makes it harder for you to sell. As you need to win both to be successful.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Buyer&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We took too long to realise the initial problem we were solving was not the top priority. There was much greater concern with data quality. Here's the results from the card sorting exercise of 88 calls. The problem we started solving placed 6th 🤦‍♀️.&lt;br /&gt;
&lt;img alt="top data problems" src="https://lukesingham.com/content/images/2023/top-problems.png" /&gt;&lt;/li&gt;
&lt;li&gt;Start with buyer validation, not user validation. Getting a commitment from a buyer is better than the commitment a user can offer you.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;User&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We spoke to many users and got introductions to their bosses. But initiating contact with users to reach the buyer, involves engaging multiple people within the same company to identify someone willing to intro you. Start with the buyer, at least you can qualify them faster, &lt;em&gt;Please Sir, do you have budget?&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="investors"&gt;Investors &lt;a class="direct-link" href="https://lukesingham.com/startup-reflections/#investors"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;I'm extremely grateful to everyone who took a bet on ilumadata. We raised a small round ($720k) from a mix of angels and a VC.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Don't be afraid of owning your failure i.e. don't let ego get in the way. I let this get in the way of regular investor updates, I'd think &lt;em&gt;I'll wait for some good news&lt;/em&gt; (that never came!). I should have been more open with investors, they are there to help you. I was afraid of being judged, but in retrospect I was judging myself more harshly than they would have.
&lt;ul&gt;
&lt;li&gt;Many investors will go into bat for you. If you're not open with them, they can't help you.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;We raised a small round but it cost 2 months of time. Did we need to raise? Whilst in YC &amp;quot;The mantra was if you &lt;em&gt;can&lt;/em&gt; raise at Demo Day, you &lt;em&gt;should&lt;/em&gt; raise&amp;quot; (see &lt;a href="https://www.lennysnewsletter.com/p/startup-to-exit-lessons-from-a-first"&gt;another YC founder's experience of this&lt;/a&gt;)
&lt;ul&gt;
&lt;li&gt;We could have proven this idea didn't work with only the YC investment. In fact, we could have validated it with no money!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Don't be driven by investors, they are not the buyer.
&lt;ul&gt;
&lt;li&gt;If they ask for a product roadmap and you are pre-PMF and pre-seed, this is a waste of time. Spend time on getting traction. I remember Siebel during our batch saying &amp;quot;chuck out your Product Roadmap!&amp;quot;&lt;/li&gt;
&lt;li&gt;I've filtered out potential co-founders based on focusing on 'investor traction' rather than customer traction.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="market"&gt;Market &lt;a class="direct-link" href="https://lukesingham.com/startup-reflections/#market"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;You don't want to be working on a 'nice to have' at the best of times. But if you are and you have a enterprise sales motion, recession fears and major tech lay offs mean most enterprise doors are closed.&lt;/li&gt;
&lt;li&gt;One early thought was - 'if we improve productivity even by a tiny fraction like 5% then using ilumadata is an obvious win'. Firstly, your adoption cost needs to be low to prove this, ours was not. Secondly, even this is not enough, you need to solve a problem and 'improving efficiency by 5%' is not a problem, let alone a painful one.&lt;/li&gt;
&lt;li&gt;Market Saturation - so many tools in this space, and it only got worse as we were out there. We thought our product was unique or at least sufficiently different, then we started finding many companies doing the same thing.&lt;/li&gt;
&lt;li&gt;Outside of the real core of a data platform, cloud data-warehouses and BI, who is getting decent revenue? I had a couple of investors that had invested elsewhere in the modern data stack that many of the big names in that space weren't making much in the way of revenue.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="see-also"&gt;See also &lt;a class="direct-link" href="https://lukesingham.com/startup-reflections/#see-also"&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.cascade.io/epilogue/the-data-and-analytics-market"&gt;Cascade - Similar startup that failed&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benn.substack.com/p/category-collapse"&gt;Category Collapse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pmarchive.com/guide_to_startups_part4.html"&gt;Guide to startups&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="legal"&gt;Legal &lt;a class="direct-link" href="https://lukesingham.com/startup-reflections/#legal"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Cease and desist - we got one. It scared us and distracted us for at least a month. Don't be scared of this. Unless - you really did break laws!
&lt;ul&gt;
&lt;li&gt;Our YC Partner congratulated us, saying it's a sign you're doing something right. It's a badge of honour.&lt;/li&gt;
&lt;li&gt;Big incumbents sometimes monopolize the leading law firms by simultaneously assigning legal matters to all of the top-tier firms. That way, the law firm you're using will have a potential conflict of interest and need the company that sent you the C&amp;amp;D to sign a waiver. Which of course they won't do! It's like a game of monopoly, and they've parked hotels on the top end of town.&lt;/li&gt;
&lt;li&gt;Get referrals to boutique specific law firms - we landed an incredible litigation firm that was very reasonable on the company purse.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="self"&gt;Self &lt;a class="direct-link" href="https://lukesingham.com/startup-reflections/#self"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Mental health - I'm not someone who has had any diagnosable mental health issues. Doing a startup is very hard. Everyone tells you this. But the lived experience is very different. Facing hundreds of rejections, many iterations, a hard pivot, co-founder separation and eventually a wind-down is tough. I remember seeing the 'trough of sorrow' graph before going through YC, it's very real!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="trough-of-sorrow" src="https://lukesingham.com/content/images/2023/trough-of-sorrow.png" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sales Led Growth is hard! Not sure if it would be different with better traction - a bit of dopamine goes a long way.&lt;/li&gt;
&lt;li&gt;I'm definitely not a perfectionist - if it works, let's ship it! I'm very comfortable with things being rough around the edges.&lt;/li&gt;
&lt;li&gt;I learnt more about what &lt;em&gt;I need&lt;/em&gt; from a co-founder. Much like your first non-platonic relationships, you learn the hard way what works and doesn't work for you.&lt;/li&gt;
&lt;li&gt;I found talking to other founders really helpful, normalising the struggles unique to startups, hearing about what they have tried, what's worked and what hasn't - it's something I do regularly now, but could have done more through the hard times. This is a huge benefit of going through an accelerator, to forge your network of entrepreneurs.&lt;/li&gt;
&lt;li&gt;CEO - this role is tough, and it's a role I don't particularly relish as a full-time position. A lot of startups in our batch had the fight over the role, many have co-CEO's until your group partner finally yells at you enough to change! For us, CEO was really who drew the short straw! Early on, I split my time between sales and dev work. After all, we had an MVP to build. 6 months in and the code base was still 40% written by me. This felt productive, but in retrospect validating the problem and willingness to pay would have been time better spent. The last few months of the venture I was pure sales/product, soft pivoting through problem areas in the modern data tooling space. When the business/tech ratio became 100% business for me, that's when I realised I didn't want to be CEO.&lt;/li&gt;
&lt;li&gt;There is an inherent duplicity that must be maintained, startups are innately difficult and full of uncertainty. But you must project confidence and optimism when raising funds, when selling to customers and to (potential) employees. All whilst you might be plagued by doubts or recovering from the last bruising 'No' you were banking was a 'Yes'! I found this balance hard to maintain.&lt;/li&gt;
&lt;li&gt;I sought consensus. Given the co-founder relationship was already strained, I didn't want to add fuel to the fire. What I should have done, was make clear areas of responsibilities, provide a short time frame for feedback and then execute. The trade-off is speed vs. consensus, and for early stage startups you don't have much time.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="essential-reading"&gt;Essential reading &lt;a class="direct-link" href="https://lukesingham.com/startup-reflections/#essential-reading"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.google.com/document/d/1pUdqNJf0hchC96ZW142vE7xKPNsWZpw_GF_PIf9pJ7k/edit"&gt;startup primer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://evis.substack.com/p/we-grew-from-0-to-150k-in-6-months?r=7zecw"&gt;https://evis.substack.com/p/we-grew-from-0-to-150k-in-6-months?r=7zecw&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="footnotes"&gt;Footnotes &lt;a class="direct-link" href="https://lukesingham.com/startup-reflections/#footnotes"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;hr class="footnotes-sep" /&gt;
&lt;section class="footnotes"&gt;
&lt;ol class="footnotes-list"&gt;
&lt;li class="footnote-item" id="fn1"&gt;&lt;p&gt;One exception to this is if you find PMF - it solves everything. I've heard of founders absolutely hating each other and that trickling down into very poor company culture. But when you're on a rocket ship to a big payout, people will stick it out 🥕! &lt;a class="footnote-backref" href="https://lukesingham.com/startup-reflections/#fnref1"&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</description><author>Luke Singham</author><pubDate>Fri, 01 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://lukesingham.com/startup-reflections/</guid></item><item><title>How to find a co-founder</title><link>https://lukesingham.com/how-to-find-a-co-founder/</link><description>&lt;p&gt;I recently went on the hunt for a new co-founder.  This ended up being my process.&lt;/p&gt;
&lt;h1 id="starting-principles"&gt;Starting principles &lt;a class="direct-link" href="https://lukesingham.com/how-to-find-a-co-founder/#starting-principles"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Meet as many as you can in parallel. Like a sales pipeline, you wouldn't only talk to a single customer at a time, nor should you for founders.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Give yourself a deadline, goal setting matters, it also creates urgency so you can close.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It takes longer than you think, several months for me and I know many others that took a lot longer.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Decide on some initial parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Geography&lt;/strong&gt;: Initially for me, this was East coast US to Europe for some timezone overlap. This narrowed to UK and western Europe. I realised having the Atlantic ocean between us was going to be too big of a hurdle to de-risk the relationship.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Profile&lt;/strong&gt;: I started with 'either a CEO or CTO type'. Typically you'll have a CEO/CTO founding combo. Being technical myself and having &lt;a href="https://lukesingham.com/startup-reflections/#self"&gt;reflected on my previous venture&lt;/a&gt;, I narrowed this to CEO.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Idea or not&lt;/strong&gt;: I was looking for someone with an idea, validated ideally.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Quit their job&lt;/strong&gt;: candidates that haven't quit don't have the same urgency. They are comfortable. And sometimes flirting with the idea of startups rather than being ready to commit now.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Not an existing duo&lt;/strong&gt;: you want to be on as equal terms with your co-founder(s) as possible. After meeting a few duos, I realised it was unlikely I would feel on the same level as the original duo.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="the-process"&gt;The process &lt;a class="direct-link" href="https://lukesingham.com/how-to-find-a-co-founder/#the-process"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;This is somewhere between dating and hiring someone. You want to keep it natural and a lot of it is about getting to know the other person. But you have a deadline and a business to start!&lt;/p&gt;
&lt;h2 id="top-of-funnel---get-swiping"&gt;Top of funnel - get swiping &lt;a class="direct-link" href="https://lukesingham.com/how-to-find-a-co-founder/#top-of-funnel---get-swiping"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In dating the 'old way' was being set up by friends. The 'new way' is dating apps. Both work for co-founder leads, but just like Tinder, you'll get many more leads through &lt;a href="https://www.ycombinator.com/cofounder-matching/"&gt;Y Combinator's co-founder matching app&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="1.-the-first-date"&gt;1. The first date &lt;a class="direct-link" href="https://lukesingham.com/how-to-find-a-co-founder/#1.-the-first-date"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is the initial meet and greet. I kept these to a 30min video call or a quick in person coffee. The goals:&lt;/p&gt;
&lt;p&gt;✅ Get to know them. Do you vibe? Yep, it's a lot about vibes. If you don't vibe on a first date with someone, you don't go on a second date.&lt;br /&gt;
✅ Do you respect them? This is really about whether you see them on eye level.&lt;/p&gt;
&lt;h2 id="2.-the-written-assignment"&gt;2. The written assignment &lt;a class="direct-link" href="https://lukesingham.com/how-to-find-a-co-founder/#2.-the-written-assignment"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The aim here is to have the most time efficient process to get to know your potential co-founder. The &lt;a href="https://proof-assets.s3.amazonaws.com/firstround/50%20Questions%20for%20Co-Founders.pdf"&gt;50 Founder questions&lt;/a&gt; get you to introspect on &lt;em&gt;why&lt;/em&gt; you're founding a company, reveals your areas of expertise and raises thorny questions early like &lt;em&gt;how would you resolve a stalemate?&lt;/em&gt; The best founders were already aware of the 50 Founder Questions. If someone hadn't heard of them, I'd give them time to get back to me.&lt;/p&gt;
&lt;p&gt;✅ Exchange &lt;a href="https://proof-assets.s3.amazonaws.com/firstround/50%20Questions%20for%20Co-Founders.pdf"&gt;50 Founder questions&lt;/a&gt; - ensure values, expertise and thinking alignment.&lt;/p&gt;
&lt;h2 id="3.-more-dates---discuss-the-founder-questions-and-any-other-questions"&gt;3. More dates - discuss the Founder Questions and any other questions &lt;a class="direct-link" href="https://lukesingham.com/how-to-find-a-co-founder/#3.-more-dates---discuss-the-founder-questions-and-any-other-questions"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If the potential co-founder's responses revealed a gap too large, I'd just say so in an email. If you only have minor flags, question marks and areas you want to dig deeper - have as many calls to work through them as required.&lt;/p&gt;
&lt;p&gt;✅ Each meeting is increasing your confidence in each other, to the point you are ready for a work trial.&lt;br /&gt;
✅ With a view to commencing a work trial, if the co-founder is committed to an idea, get across the idea.&lt;sup class="footnote-ref"&gt;&lt;a href="https://lukesingham.com/how-to-find-a-co-founder/#fn1" id="fnref1"&gt;[1]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h2 id="4.-part-time-work-trial"&gt;4.  Part time work trial &lt;a class="direct-link" href="https://lukesingham.com/how-to-find-a-co-founder/#4.-part-time-work-trial"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;By this stage you've worked through values, any possible question marks you've had about each other and are ready to test out what it is like to work with each other. I did this with several people at the same time. Using the dating analogy, this is that tricky and tiring phase where you haven't committed to one person yet. You're switching contexts and trying to not mix up details about each person! The work trial can look quite different depending on each person. For example, with one they hadn't found an idea, so the working together was about making progress through the ideation phase. For another, it was about doing joint discovery calls and validating ideas.&lt;/p&gt;
&lt;p&gt;✅ Complete a timeboxed work trial together.&lt;/p&gt;
&lt;h2 id="5.-commit"&gt;5. Commit &lt;a class="direct-link" href="https://lukesingham.com/how-to-find-a-co-founder/#5.-commit"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This doesn't mean you have incorporated. But from step 4 you need to pick one person to move forward. And hopefully you have a strong sense of who the right person is. Having committed to each other, set yourself ambitious goals that are aligned to each of your primary areas of responsibility. For me that looked like developing a prototype and for my co-founder it was securing design partnerships. If this all goes well, you're already a company, you just need to sign the dotted line.&lt;/p&gt;
&lt;p&gt;Good luck!&lt;/p&gt;
&lt;h1 id="other-inspiration"&gt;Other inspiration &lt;a class="direct-link" href="https://lukesingham.com/how-to-find-a-co-founder/#other-inspiration"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://whalesync.notion.site/TEMPLATE-Cofounder-Trial-Period-cbc8b29c068c4150b9ccd71f40291f6e"&gt;Whalesync's trial period template&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.google.com/document/d/1rXUOP-FcnIE8eNTkKlELkakZ-MLaIvEyIxUlBOLNZPw/edit"&gt;YCs doc on co-founder matching&lt;/a&gt; my highlights:
&lt;ol&gt;
&lt;li&gt;Someone you enjoy spending time with and get along with. &lt;strong&gt;Would I want to be friends with this person?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;An exceptional person - smart and effective&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;If you have hesitancy towards someone, listen to it.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Find people you have things in common with&lt;/strong&gt; - shared interests, hobbies and values&lt;/li&gt;
&lt;li&gt;The person is more important than the idea&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The median time between when the founders joined the site and when they matched with the person they ultimately ended up working with is 100 days&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/entrepreneur-first/the-co-founder-checklist-2f5be597e887"&gt;EF advice&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Both co-founders are technical, or technical with a domain expert&lt;/li&gt;
&lt;li&gt;Both co-founders are involved in everything&lt;/li&gt;
&lt;li&gt;No ‘pity founding’ - we're mates!&lt;/li&gt;
&lt;li&gt;A co-founder isn’t a ‘must-have’, a generic role that needs to be filled... they are a vital value-add&lt;/li&gt;
&lt;li&gt;&amp;quot;Each co-founder needs to have a strong relationship with each co-founder. In our experience, in a three this often isn’t the case. Someone is usually making a compromise and although that may seem ok in the short term, this doesn’t lead to harmonious founding teams in the long term.&amp;quot;&lt;/li&gt;
&lt;li&gt;you have, or can quickly acquire the skills needed to build the product you want to build&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.paulgraham.com/really.html"&gt;Paul Graham&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;You haven't seen someone's true colors unless you've worked with them on a startup.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;the relationship between founders was more important than ability&amp;quot;&lt;/li&gt;
&lt;li&gt;Share &lt;a href="https://www.startupschool.org/faq"&gt;startupschool FAQs&lt;/a&gt; with them - aim to pick a trial project to work on together&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;More questions to ask each other from &lt;a href="https://docs.google.com/document/d/1L4wY9qRk75K7B5y9_unueE4Tw0YLRarA9vaCTwfwL-4/edit#heading=h.xhucswxvxook"&gt;YCs doc here&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr class="footnotes-sep" /&gt;
&lt;section class="footnotes"&gt;
&lt;ol class="footnotes-list"&gt;
&lt;li class="footnote-item" id="fn1"&gt;&lt;p&gt;This is a good opportunity to stress test the relationship too. Really question the idea, poke holes, debate it and see how they respond. What would it take to work on something else? You probably need to pivot in the future, which is a difficult conversation if you don't arrive at it at the same time. &lt;a class="footnote-backref" href="https://lukesingham.com/how-to-find-a-co-founder/#fnref1"&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</description><author>Luke Singham</author><pubDate>Fri, 01 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://lukesingham.com/how-to-find-a-co-founder/</guid></item><item><title>Legacy Is Sexy</title><link>https://backendhance.com/blog/2023/legacy-is-sexy/</link><description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Unpopular Opinion Warnung: Legacy ist sexy!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Um ehrlich zu sein - ich habe das nicht immer so gesehen.&lt;/p&gt;
&lt;p&gt;Dir ging es bestimmt ähnlich.&lt;/p&gt;
&lt;h2 id="mein-erstes-großes-professionelle-projekt-war-eine-legacy-anwendung"&gt;
&lt;a class="anchor" href="#mein-erstes-gro%c3%9fes-professionelle-projekt-war-eine-legacy-anwendung"&gt;Mein erstes (großes) professionelle Projekt war eine Legacy Anwendung&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;So alt war sie eigentlich gar nicht. Wenn ich mich richtig erinnere, dann hatte sie nicht mal ein Jahrzehnt auf dem Buckel.&lt;/p&gt;
&lt;p&gt;Es fühlte sich aber viel älter an.&lt;/p&gt;
&lt;p&gt;Dies lag hauptsächlich an den vielen unerfahrenen Entwicklern, die an der Anwendung arbeiteten.&lt;/p&gt;</description><author>Backendhance</author><pubDate>Fri, 01 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/legacy-is-sexy/</guid></item><item><title>Tea ratings</title><link>https://vit.baisa.cz/notes/personal/tea/</link><description>list of teas with notes and rating</description><author>Vít Baisa</author><pubDate>Fri, 01 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://vit.baisa.cz/notes/personal/tea/</guid></item><item><title>Town Hall #17: Scrimmage</title><link>https://taylor.town/town-hall-0017</link><description>Creation is a toilsome, boring, everyday thing. Historic plays can't be planned in advance.</description><author>taylor.town</author><pubDate>Fri, 01 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/town-hall-0017</guid></item><item><title>Getting EC2 Instance Metadata Using IMDSv2</title><link>https://nelson.cloud/getting-ec2-instance-metadata-using-imdsv2/?ref=rss</link><description>Curl examples for retrieving EC2 instance metadata using IMDSv2 including IP address, instance ID, and security groups.</description><author>Nelson Figueroa</author><pubDate>Fri, 01 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://nelson.cloud/getting-ec2-instance-metadata-using-imdsv2/?ref=rss</guid></item><item><title>Documenting Laravel APIs Talk Video</title><link>https://james.brooks.page/blog/documenting-laravel-apis-talk</link><description>Watch my Documenting Laravel APIs talk from Laracon AU.</description><author>James Brooks</author><pubDate>Fri, 01 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://james.brooks.page/blog/documenting-laravel-apis-talk</guid></item><item><title>What's new in Seastar - issue 2</title><link>https://makedist.com/posts/2023/12/01/whats-new-in-seastar-issue-2/</link><description>A summary of recent developments in the &lt;a href="https://seastar.io/"&gt;C++ Seastar project&lt;/a&gt;.</description><author>Noah Watkins</author><pubDate>Fri, 01 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://makedist.com/posts/2023/12/01/whats-new-in-seastar-issue-2/</guid></item><item><title>Understanding Objective-C by transpiling it to C++</title><link>https://www.jviotti.com/2023/12/01/understanding-objective-c-by-transpiling-it-to-cpp.html</link><description>This article describes how to transpile Objective-C to C++, and use that approach to gain understanding of the Objective-C runtime</description><author>Juan Cruz Viotti</author><pubDate>Fri, 01 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.jviotti.com/2023/12/01/understanding-objective-c-by-transpiling-it-to-cpp.html</guid></item><item><title>December 2023 - Bookmarks</title><link>https://domenicoluciani.com/2023/12/01/bookmarks.html</link><description>Bookmarks for December 2023: 1 link - BeckDesignRules.</description><author>Domenico Luciani</author><pubDate>Fri, 01 Dec 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://domenicoluciani.com/2023/12/01/bookmarks.html</guid></item><item><title>2023-11-30-001</title><link>https://srijan.ch/notes/2023-11-30-001</link><description>Found Samuel's nice post on capturing data for org via email. This is very close to what I was looking for to be able to do GTD capture on-the-go either from phone apps like Braintoss or from any email app. One addition I would like to make is handling attachments in the email by downloading them and attaching to the org entry. This would be useful for voice notes from Braintoss - it does …</description><author>Srijan Choudhary, all posts</author><pubDate>Thu, 30 Nov 2023 20:10:00 GMT</pubDate><guid isPermaLink="true">https://srijan.ch/notes/2023-11-30-001</guid></item><item><title>Dissapointing Framework Laptop 13 DIY Edition</title><link>https://cobbaut.blogspot.com/2023/11/dissapointing-framework-laptop-13-diy.html</link><description>&lt;p&gt;&lt;b&gt;What a disaster this is, the DIY &lt;a href="https://frame.work/be/en" rel="nofollow" target="_blank"&gt;framework&lt;/a&gt; laptop.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;The box...&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLGB_VcvdNoLfXYDM68CBzERwEmGQsEgL2ym4GWKW1em-Mr68bqw2VtsYq_1Aysl9XC7kVbvX4N-3XY8XYf0lvMZMCgXcsPjdlQT_9BoM8rSKQYADoJCHrILXTlvOFLgCxf3S4C-1Gk2Mkc7Yv5GKDMqGly6hjtM7s1sKXiXaTvdxHakmxZg-v/s1407/01box.jpg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="258" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLGB_VcvdNoLfXYDM68CBzERwEmGQsEgL2ym4GWKW1em-Mr68bqw2VtsYq_1Aysl9XC7kVbvX4N-3XY8XYf0lvMZMCgXcsPjdlQT_9BoM8rSKQYADoJCHrILXTlvOFLgCxf3S4C-1Gk2Mkc7Yv5GKDMqGly6hjtM7s1sKXiXaTvdxHakmxZg-v/s320/01box.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;...contains more boxes.&lt;br /&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgLzBVzkxDDMC_kUuJ-a_AMnu0CAJGMDnzUuhaU3Okoo5SEfF5vTfOcWOjyXiiJoJwl3-R0Xeo3l7l1gP8kkd5KVFW5CoBk9kWNCzmj7icP5VcjGPp-AgTXAsJLHlDpPfGK_Sefm8GdrgVUBAvO9GTXvBIijUOnTg7An4mxWbZdPyUEd52Rj5hp/s1499/02_unbox.jpg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="242" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgLzBVzkxDDMC_kUuJ-a_AMnu0CAJGMDnzUuhaU3Okoo5SEfF5vTfOcWOjyXiiJoJwl3-R0Xeo3l7l1gP8kkd5KVFW5CoBk9kWNCzmj7icP5VcjGPp-AgTXAsJLHlDpPfGK_Sefm8GdrgVUBAvO9GTXvBIijUOnTg7An4mxWbZdPyUEd52Rj5hp/s320/02_unbox.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;p&gt;The parts are few...&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXBAVM6dhDjdipM56J8jUOq7z8p74kyDNh73P2J_eB-QHa3hO1RP8DJETyy2vpB53_guCjlM8_AwlSKSm_KyCqNRYbXf1ioB_X2YsAOdOpN4pGi3TBkykKTB97flvmKLEuaGwp73aGooM23lRibQ1tAgoUmx5RRPc-yIYN4yRcK01R_NGIWQZj/s1750/03_parts.jpg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="207" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXBAVM6dhDjdipM56J8jUOq7z8p74kyDNh73P2J_eB-QHa3hO1RP8DJETyy2vpB53_guCjlM8_AwlSKSm_KyCqNRYbXf1ioB_X2YsAOdOpN4pGi3TBkykKTB97flvmKLEuaGwp73aGooM23lRibQ1tAgoUmx5RRPc-yIYN4yRcK01R_NGIWQZj/s320/03_parts.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;...so the laptop is assembled in less than a minute?!&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVt-Br5Xwy1PPlwCc7v7qLREzHpAl-PdLIoM8BeCIMbEh1qp8vEe_tshvt0iyXZ97zR3OiXpxdsY5twiMhKjXNG1uTqhCd-cjzv5aes9hKCYRQGcpURpf03Qpnm92GTpc2QWOpEZ_3j4zZ2zBYsah0U8GnYIpN6dclD0no3DvqGJJoo-EDqZ5Y/s1591/04_done.jpg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="228" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVt-Br5Xwy1PPlwCc7v7qLREzHpAl-PdLIoM8BeCIMbEh1qp8vEe_tshvt0iyXZ97zR3OiXpxdsY5twiMhKjXNG1uTqhCd-cjzv5aes9hKCYRQGcpURpf03Qpnm92GTpc2QWOpEZ_3j4zZ2zBYsah0U8GnYIpN6dclD0no3DvqGJJoo-EDqZ5Y/s320/04_done.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Unpacking the thing took longer than assembling it!&lt;/p&gt;&lt;p&gt;Reading &lt;a href="https://guides.frame.work/Guide/Framework+Laptop+13+(AMD+Ryzen%E2%84%A2+7040+Series)+DIY+Edition+Quick+Start+Guide/211?lang=en" rel="nofollow" target="_blank"&gt;this webpage&lt;/a&gt; took longer than assembling it!&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Installing the DDR5 memory took 1 second: click and done.&lt;br /&gt;Installing the nvme took 3 seconds: unscrew one screw, click and screw.&lt;br /&gt;Installing the bezel was far more challenging, took almost 30 seconds! (*)&lt;br /&gt;Attaching the keyboard: about three seconds?&lt;br /&gt;Installing the four modules: 1 second per module (or less?)&lt;/p&gt;&lt;p&gt;&lt;b&gt;I'm not that good at irony, but I was hoping for at least 20 minutes of fun assembling this thing... and only got one minute. :-/&lt;/b&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;(*) This gave fond memories of assembling tower PC's in the nineties; 
ISA/PCI slots click, CD-ROM atapi click, hard disk and IDE jumpers easy.
 But closing the tower case... well that was the hard part!&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;u&gt;Installing an OS&lt;/u&gt;&lt;/b&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;The guide says to be patient when booting (powering on) the laptop for the first time, it may take several minutes.&lt;/p&gt;&lt;p&gt;I timed it, it took 57 seconds to get to this screen:&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgH5KSeTsay5Yjn-0ZyX53yWECuM49a1K0rz6CPfEihBBSIevh0KpXGit0_EmOPe9dBb9MBW0WR8y7j7YJG3cgLAvzT6D7R6h6Obykt7DJ9Ai9E6hspzJopH_MW_ebKYi4XJDl8IyDB_YD7gb3-TLRdDVzyEw8peMiqzGcr58ctu8oUi_-ZAHDK/s1524/05_install_debian.jpg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="238" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgH5KSeTsay5Yjn-0ZyX53yWECuM49a1K0rz6CPfEihBBSIevh0KpXGit0_EmOPe9dBb9MBW0WR8y7j7YJG3cgLAvzT6D7R6h6Obykt7DJ9Ai9E6hspzJopH_MW_ebKYi4XJDl8IyDB_YD7gb3-TLRdDVzyEw8peMiqzGcr58ctu8oUi_-ZAHDK/s320/05_install_debian.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;And now there is this; it has been a long time since I saw a default XFCE, Firefox and terminal.&lt;br /&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2C6EzpMA3ACETutC59D_zg9tKHf2J_js4rFHhECtqVmUm31dVuBU5jP102awAZhV5Fx0I5m4IyE4RzoYyfiV49j4WKvb11HLi62_hTQdPPtjqj0d9W8IgejZn5diCJ6y_DcVKrOYX2LwQWj-Ec-W3DLbT69-nNRyVuCxhS7SnpsvMAiX5iBVd/s2256/06_install_finished.jpg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="213" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2C6EzpMA3ACETutC59D_zg9tKHf2J_js4rFHhECtqVmUm31dVuBU5jP102awAZhV5Fx0I5m4IyE4RzoYyfiV49j4WKvb11HLi62_hTQdPPtjqj0d9W8IgejZn5diCJ6y_DcVKrOYX2LwQWj-Ec-W3DLbT69-nNRyVuCxhS7SnpsvMAiX5iBVd/s320/06_install_finished.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Let the fun begin!&lt;br /&gt;</description><author>Paul Cobbaut's blog</author><pubDate>Thu, 30 Nov 2023 17:53:18 GMT</pubDate><guid isPermaLink="true">https://cobbaut.blogspot.com/2023/11/dissapointing-framework-laptop-13-diy.html</guid></item><item><title>Discord and AI GTM</title><link>https://mattrickard.com/discord-and-ai-gtm</link><description>Midjourney is the largest discord server, with 16.5 million total users. It accounts for 13% of total Discord invites. Midjourney launched in March 2022 and doe</description><author>Matt Rickard</author><pubDate>Thu, 30 Nov 2023 16:30:00 GMT</pubDate><guid isPermaLink="true">https://mattrickard.com/discord-and-ai-gtm</guid></item><item><title>Clean 架构下的现代 Android 架构指南</title><link>https://0xzhangke.github.io/computer/2023/11/30/clean_for_android.html</link><description>&lt;p&gt;Clean 架构是 Uncle Bob 提出的一种软件架构，Bob 大叔同时也是 SOLID 原则的命名者。&lt;/p&gt;

&lt;p&gt;Clean 架构图如下：
&lt;img alt="" src="/assets/img/post/clean-android/clean.png" /&gt;&lt;/p&gt;

&lt;p&gt;这张图描述的是整个软件系统的架构，而不是单体软件，其中至少包括服务端以及客户端。&lt;/p&gt;

&lt;p&gt;对于 Android 单体应用开发来说应该还需要一个更贴切更精确的 Clean 架构图。&lt;/p&gt;

&lt;p&gt;我大概总结了一下过往的开发经验，找出了应用架构中的重要部分，然后绘制了下面这张 Clean 架构指导下的 Android 应用架构图：&lt;/p&gt;

&lt;p&gt;&lt;img alt="" src="/assets/img/post/clean-android/clean_android.png" /&gt;&lt;/p&gt;

&lt;p&gt;以及一般数据流向图：&lt;/p&gt;

&lt;p&gt;&lt;img alt="" src="/assets/img/post/clean-android/data_path.png" /&gt;&lt;/p&gt;

&lt;h1 id="依赖关系"&gt;依赖关系&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Clean 架构基本准则是源码级别的内层不依赖外层，依赖关系永远是单向的，外层向内层依赖。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;如上，Model 层是没有任何依赖的，UseCase 可以依赖 Model 和 Repo 等，但绝不能依赖 ViewModel，UI 层依赖 ViewModel，但 ViewModel 绝不能依赖 UI 层。&lt;/p&gt;

&lt;p&gt;为了达到这种源码级别的依赖关系，我们必须借助一些工具来实现依赖注入，一般可以使用 Hilt 或者 Koin 这样的框架来实现。&lt;/p&gt;

&lt;p&gt;另外，依赖注入不应该被滥用，不是所有的对象都适合用依赖注入，只有那些有明确层次关系的模块，互相有着明确的依赖关系的才需要。对于一些工具类，显然是没必要注入的。&lt;/p&gt;

&lt;h1 id="model领域模型"&gt;Model（领域模型）&lt;/h1&gt;

&lt;p&gt;业务模型，或者叫领域模型，是根据软件业务设计出来的具体模型，一般来说会是个 &lt;code class="language-plaintext highlighter-rouge"&gt;data class&lt;/code&gt;，其中不包含任何业务逻辑，只是个单纯的模型对象。&lt;/p&gt;

&lt;p&gt;由于是在整个架构的最内层，所以&lt;strong&gt;不依赖任何其他模块，并且相对稳定&lt;/strong&gt;，设计的时候需要考虑这点。如果模型发生变化，那意味着整个上层的依赖方都可能发生变化，需要重新测试。&lt;/p&gt;

&lt;p&gt;在命名和包结构上，领域模型不需要带 Entity 之类的后缀，直接命名为像 User 一样即可，但考虑到这是在软件的最内层，可能会被所有模块依赖到，所以要尽可能贴近其设计目标，并且不能太过宽泛。在包结构上，需要被存放在 model 包下面。&lt;/p&gt;

&lt;h1 id="adapter数据适配器"&gt;Adapter（数据适配器）&lt;/h1&gt;

&lt;p&gt;数据适配器层主要用来做数据转换，主要有两个职责：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;转换网络接口实体数据类和领域模型。&lt;/li&gt;
  &lt;li&gt;领域模型之间的互相转换。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Adapter 层也比较纯粹，&lt;strong&gt;只负责简单的数据转换，而且对外暴漏的函数都是幂等函数&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;如果数据转换过程中涉及到复杂的业务逻辑，可以考虑先用 UseCase 处理完成后再交给 Adapter。但因为 Adapter 层比 UseCase 层更靠内，所以 Adapter 不能依赖 UseCase。&lt;/p&gt;

&lt;p&gt;习惯上，我们会以待转换类为开头，Adapter 结尾命名，例如我们要把 &lt;code class="language-plaintext highlighter-rouge"&gt;UserEntity&lt;/code&gt; 转换为 &lt;code class="language-plaintext highlighter-rouge"&gt;User&lt;/code&gt;,  那么应该这么写：&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;UserEntityAdapter&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    
    &lt;span class="nx"&gt;fun&lt;/span&gt; &lt;span class="nx"&gt;toUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserEntity&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;//...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id="repo"&gt;Repo&lt;/h1&gt;

&lt;p&gt;对于我们 Android 开发来说，Repo 层应该是对网络接口或本地磁盘的数据读写的封装，对于 Repo 的使用者来说，不需要关注具体的实现，且 Repo 中一般不具备复杂的业务逻辑，只能包含简单的数据处理逻辑。&lt;/p&gt;

&lt;p&gt;Repo 应当隐藏具体的实现细节，不仅包括获取方式是网络还是本地数据，也应该隐藏对应的实体数据类，这意味着 &lt;strong&gt;Repo 层对外暴漏的函数的入参和出参不能包含接口返回的实体类，也不应该包含数据库表实体类，只能包含领域模型或者基本类型。&lt;/strong&gt;我们给 Room 设计的数据库表的 &lt;code class="language-plaintext highlighter-rouge"&gt;data class&lt;/code&gt; 应该限制在 Repo 内部，我们给 Retrofit 设计的接口返回数据 &lt;code class="language-plaintext highlighter-rouge"&gt;data class&lt;/code&gt; 也同样应该限制在 Repo 内部。&lt;/p&gt;

&lt;div class="language-kotlin highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;UserEntity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;avatar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;UserService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/user"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getUserInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;UserEntity&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;avatar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserEntityAdapter&lt;/span&gt; &lt;span class="nd"&gt;@Inject&lt;/span&gt; &lt;span class="k"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;toUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;UserEntity&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;avatar&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;avatar&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserRepo&lt;/span&gt; &lt;span class="nd"&gt;@Inject&lt;/span&gt; &lt;span class="k"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;userEntityAdapter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;UserEntityAdapter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;userService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;UserService&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;lazy&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;retrofit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;userService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUserInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userEntityAdapter&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;toUser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;除了上面说的相应数据的转换之外，&lt;strong&gt;请求数据也需要在 Repo 层转换&lt;/strong&gt;，对于 Post 请求来说，可能会存在一个请求实体，这个实体数据类最好也不要对外暴漏，可以在 Repo 层的请求方法入参那里做一些转换，最好能让入参更简单友好。&lt;/p&gt;

&lt;p&gt;Repo 层还有一个作用就是&lt;strong&gt;负责把从接口或者数据库中出来的不友好的数据模型转换成友好的数据模型&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;另外，现在由于有了 BFF 的存在，在某些比较简单的业务场景下我们可以为了方便做一些妥协，也就是接口的响应数据实体类可以穿透 Repo 层，直接给到 ViewModel，甚至是 UiState 使用，但应该明白这&lt;strong&gt;只是为了方便的妥协，并不是最佳实践，需要严格控制影响范围&lt;/strong&gt;。&lt;/p&gt;

&lt;h1 id="usecase用例"&gt;UseCase（用例）&lt;/h1&gt;

&lt;p&gt;UseCase &lt;strong&gt;一般是指特定应用场景下的业务逻辑&lt;/strong&gt;，用例引导了数据在模型之间的输入输出，并且指挥着业务实体利用其中的关键业务逻辑来实现用例的设计目标。&lt;/p&gt;

&lt;p&gt;因此，一个 UseCase 往往&lt;strong&gt;只包含一段具体的业务逻辑，他的输入是基本类型或者领域模型，输出也是，并且是幂等函数，也就是纯函数&lt;/strong&gt;，所以 Google 建议我们每个 UseCase 只包含一个公开的函数，类似于下面这种写法：&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;DoSomethingUseCase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
		&lt;span class="nx"&gt;operator&lt;/span&gt; &lt;span class="nx"&gt;fun&lt;/span&gt; &lt;span class="nx"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;xxx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Bar&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
				&lt;span class="c1"&gt;// ...&lt;/span&gt;
		&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;通过利用 Kotlin 特性来使 UseCase 在使用的时候达到直接使用函数的体感。&lt;/p&gt;

&lt;p&gt;但考虑到依赖以及管理问题，UseCase 最好还是不要直接使用函数来实现，应当按照上面的方式，定义一个类，然后再暴露一个通过操作符重载的函数。&lt;/p&gt;

&lt;p&gt;在使用 UseCase 时可以这么用：&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;LoginViewModel&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Inject&lt;/span&gt; &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt; &lt;span class="nx"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DoSomethingUseCase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;ViewModel&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
    
    &lt;span class="nx"&gt;fun&lt;/span&gt; &lt;span class="nx"&gt;onLoginClick&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
        &lt;span class="nx"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id="usecase-的问题"&gt;UseCase 的问题&lt;/h3&gt;

&lt;p&gt;UseCase 的粒度非常细，基本上每个 UseCase 就是一个函数，在复杂的业务背景下将会存在非常多的 UseCase，随着业务的增加，对他们的管理将难以为继。&lt;/p&gt;

&lt;p&gt;因此，UseCase 需要一个有效的手段来进行管理，首先，应当按功能对他们的包名进行划分。同一个业务的 UseCase 最好具备相同的包名。&lt;/p&gt;

&lt;p&gt;其次，我们不能陷入所有业务都用 UseCase 的极端情况中，很多时候，我们可以将一些极度类似的功能组织在一个类中，其中提供多个公开的方法，这样的写法在以前很常见，比如各种 Manager, Helper, Resolver 等，他们能有效的减少 UseCase 数量，并且相对简单。&lt;/p&gt;

&lt;h1 id="uistate"&gt;UiState&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;UiState 是用来描述当前 UI 状态的集合类，一般来说应该是个 data class。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;UiState 一定是不可变类，如果希望更改其中的某个值，应当重新创建一个对象，直接通过 data class 提供的 copy 方法即可，例如：&lt;/p&gt;

&lt;div class="language-kotlin highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;LoginUiState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;avatar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;consentAgreed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onAgreeChecked&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
  &lt;span class="n"&gt;uistate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uiState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;consentAgreed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;UiState 中的数据应当尽可能的方便给 UI 直接使用&lt;/strong&gt;，因为 UiState 本身就是为了 UI 设计的，例如对于一个需要显示的格式化后的时间，格式化的逻辑最好放在 ViewModel 或者更内层，而不是直接给 UiState 一个时间戳，让 UI 层去格式化。很多时候看起来简单的逻辑也可能犯错误，UI 层没有能力处理异常。&lt;/p&gt;

&lt;p&gt;在 ViewModel 中如果需要更新 UiState，可以直接通过 &lt;code class="language-plaintext highlighter-rouge"&gt;update&lt;/code&gt; 方法。&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nx"&gt;_uiState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zhangke&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id="viewmodel"&gt;ViewModel&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;ViewModel 负责管理 UI 状态，执行对应的业务逻辑。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;因此 ViewModel 的生命周期与页面是一致的。&lt;/p&gt;

&lt;p&gt;一般来说我们会通过直接使用 Jetpack 提供的 ViewModel，但也可以自己创建其他类型的 ViewModel，只要控制好生命周期即可。&lt;/p&gt;

&lt;p&gt;ViewModel 主要负责两件事情：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;对外提供当前 UI 状态&lt;/li&gt;
  &lt;li&gt;接收 UI 事件并作出响应&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;当前 UI 状态我们通过将 UiState 包装在 StateFlow 里对外提供。&lt;/p&gt;

&lt;div class="language-kotlin highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;_uiState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MutableStateFlow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;uiState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;StateFlow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UserUiState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_uiState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asStateFlow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;接受 UI 事件这点需要注意，ViewModel 需要做的是接收 UI 事件，例如用户手势输入，至于用户点击之后要做什么事情这是 ViewModel 的内部逻辑，不应该对外暴露。&lt;/p&gt;

&lt;h1 id="ui-层"&gt;UI 层&lt;/h1&gt;

&lt;p&gt;我们这里说的 UI 层就是指一个页面，除了常规的 Activity/Fragment 之外，对于 Compose 来说一个页面可能对应的是一个 Composable 函数，这取决于 UI 层的实现。&lt;/p&gt;

&lt;p&gt;UI 层应该完全是数据驱动的，UI 层的作用就是百分之百的将 UiState 渲染出来，UiState 发生变化，UI 也跟着变化，这一点声明式 UI 框架做的很好。&lt;/p&gt;

&lt;p&gt;UI 层虽然也可以处理一些简单的事件，但大部分的事件都还是要交给 ViewModel 来处理。&lt;/p&gt;

&lt;p&gt;以上就是 Android 整洁架构中的一些关键概念的介绍，我已经按照这个架构开发了一年多了，目前看下来确实会让架构很整洁，但对于一些复杂的业务场景，尤其是可能需要穿透多个层级，跨越常规生命周期的模块就需要更精细的设计了。&lt;/p&gt;</description><author>张可的博客</author><pubDate>Thu, 30 Nov 2023 14:24:30 GMT</pubDate><guid isPermaLink="true">https://0xzhangke.github.io/computer/2023/11/30/clean_for_android.html</guid></item><item><title>Oh no second</title><link>https://ilearnt.com/blog/ohnosecond/</link><description>&lt;p&gt;An oh no second is that moment in time when you realise that you have done something potentially catastrophic and your stomach turns to mush with the dread and realisation.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Thu, 30 Nov 2023 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/ohnosecond/</guid></item><item><title>Comic strip: long day at the office</title><link>https://zackproser.com/blog/comic-strip-long-day</link><description>A wordless comic strip about a typically brutal day at work, that nevertheless has a positive ending</description><author>Zachary Proser</author><pubDate>Thu, 30 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/comic-strip-long-day</guid></item><item><title>cram those ideas into a clown car</title><link>https://taylor.town/clown-car</link><description>To make memorable art, compress arguments/emotions into potent vehicles.</description><author>taylor.town</author><pubDate>Thu, 30 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/clown-car</guid></item><item><title>Epithet, Briefly</title><link>https://skife.org/b4/epithet-briefly/</link><description>&lt;p&gt;Since &lt;a href="https://hachyderm.io/@bobmcwhirter/111496958192961901#." rel="external"&gt;Bob asked&lt;/a&gt;, I'm cleaning up a system to make using short-lived ssh certificates easy, simple, and secure.&lt;/p&gt;
&lt;p&gt;The gist is to use a custom ssh-agent which, in turn, uses some modern authn service (OIDC, Keycloak, Okta, Google Sign-In, etc) to authenticate you. It then passes that auth token up to a CA which relies on a policy service to verify the auth token and respond with cert params (time, principals, extensions, whatever is needed). The CA uses the policy provided attributes to generate a short lived cert, which it gives back to the agent. Voila, you can access things for a couple minutes.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Epithet Boxes and Arrows" src="https://skife.org/b4/epithet-briefly/epithet-boxes-arrows.svg" title="Epithet" /&gt;&lt;/p&gt;
&lt;p&gt;There is a real sequence diagram of what is going on &lt;a href="https://github.com/epithet-ssh/epithet/blob/master/README.md" rel="external"&gt;in the repo&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="ca"&gt;CA&lt;/h2&gt;
&lt;p&gt;The CA is dirt simple and is the only thing that will ever see the CA private key. The same CA service should be usable by more or less anyone, maybe with some variety in terms of where the private key is stored, but that is it.&lt;/p&gt;
&lt;p&gt;The CA receives an opaque auth token + pubkey from the agent and responds with a cert. The existing implementation uses a policy service to decide whether to issue the cert, and what attributes to give it.&lt;/p&gt;
&lt;p&gt;The actual CA was kept as simple as possible, because it is a CA. You don't want to muck with it much.&lt;/p&gt;
&lt;h2 id="policy"&gt;Policy&lt;/h2&gt;
&lt;p&gt;The policy service consumes the opaque auth tokens from the CA and reponds with basic info, assuming the auth is valid:&lt;/p&gt;
&lt;pre class="giallo" style="color: #24292E; background-color: #FFFFFF;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #005CC5;"&gt;    &amp;quot;identity&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #032F62;"&gt;&amp;quot;brianm@skife.org&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #005CC5;"&gt;    &amp;quot;principals&amp;quot;&lt;/span&gt;&lt;span&gt;:[&lt;/span&gt;&lt;span style="color: #032F62;"&gt;&amp;quot;oncall_fluffy&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #032F62;"&gt; &amp;quot;oncall_wibble&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #032F62;"&gt; &amp;quot;wheel&amp;quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #005CC5;"&gt;    &amp;quot;expiration&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #032F62;"&gt;&amp;quot;2m&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #005CC5;"&gt;    &amp;quot;extensions&amp;quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #005CC5;"&gt;        &amp;quot;permit-pty&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #005CC5;"&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #005CC5;"&gt;        &amp;quot;permit-port-forwarding&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #005CC5;"&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The policy server (single endpoint, takes a post with the context for the request) is nice as it can be dynamic and make use of risk models and various other signals to decide what to do: eg, "is it a deploy server asking for a key? Is there actually a deploy in progress matching the identity requesting the cert? Okay, give it a 5 minute cert" on the sophisticated side; simply checking an OIDC token for validity on the simpler side.&lt;/p&gt;
&lt;h2 id="agent"&gt;Agent&lt;/h2&gt;
&lt;p&gt;The ssh agent generates a new keypair on startup and never lets the private key leave memory. It has a small GRPC API over a domain socket for interfacing with authentication mechanisms, such as a &lt;a href="https://github.com/epithet-ssh/epithet/blob/master/cmd/epithet-auth/epithet-auth.go" rel="external"&gt;helper to simply feed it tokens over stdin&lt;/a&gt; for simple cases. We popped a browser (or used a &lt;a href="https://github.com/brianm/oktadance" rel="external"&gt;cli tool&lt;/a&gt;) to do the Okta dance.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/micbase" rel="external"&gt;Michael&lt;/a&gt; and I made this at a previous company. We spun up the CA in one lambda function, the policy server in another, and authed against Okta. I want it for my own stuff now and it doesn't look like anyone there is maintaining it anymore, so I have been bringing it back to life.&lt;/p&gt;</description><author>Brian's Waste of Time</author><pubDate>Thu, 30 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://skife.org/b4/epithet-briefly/</guid></item><item><title>Windows WPF via VM On Mac</title><link>https://chrisfrew.in/blog/windows-wpf-via-vm-on-a-mac/</link><description>Start developing and running WPF applications on a Mac in no time!</description><author>Chris' Full Stack Blog RSS Feed</author><pubDate>Thu, 30 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://chrisfrew.in/blog/windows-wpf-via-vm-on-a-mac/</guid></item><item><title>Conditioning is grouping by</title><link>https://alexmolas.com/2023/11/30/conditioning-is-grouping-by.html</link><description>This insightful exploration draws parallels between mathematical formulations and practical implementations, showcasing how understanding conditioning as grouping can elevate your statistical insights. Discover the parallels between conditional expectations like $\mathbb{E}(y | X=x)$ and popular grouping techniques found in data analysis tools like pandas. Uncover the hidden synergy that exists between statistical conditioning and groupby operations, demystifying complex mathematical concepts with real-world applications. Whether you're a seasoned data scientist or a curious learner, this journey into the interconnected world of conditioning and grouping promises newfound clarity. Elevate your statistical understanding with practical examples, bridging the gap between theory and application. Embark on this enlightening exploration today and revolutionize your approach to statistical modeling. Uncover the simplicity behind complex conditional expressions through the power of grouping, transforming your data analysis skills along the way.</description><author>Alex Molas Blog</author><pubDate>Thu, 30 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://alexmolas.com/2023/11/30/conditioning-is-grouping-by.html</guid></item><item><title>Making noisy SVGs</title><link>https://daniel.do/article/making-noisy-svgs</link><description>Adding noise texture with only code</description><author>Daniel Immke</author><pubDate>Thu, 30 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://daniel.do/article/making-noisy-svgs</guid></item><item><title>OnlineOrNot Diaries 16</title><link>https://maxrozen.com/onlineornot-diaries-16</link><description>On content marketing, starting a YouTube channel, and improving status pages</description><author>Max Rozen</author><pubDate>Wed, 29 Nov 2023 19:10:00 GMT</pubDate><guid isPermaLink="true">https://maxrozen.com/onlineornot-diaries-16</guid></item><item><title>Standard Causes of Human Misjudgment (Munger)</title><link>https://mattrickard.com/standard-causes-of-human-misjudgment-munger</link><description>In 1995, Charlie Munger gave a speech at Harvard on *[The Psychology of Human Misjudgment](https://www.youtube.com/watch?v=Jv7sLrON7QY)**.* It was filled with t</description><author>Matt Rickard</author><pubDate>Wed, 29 Nov 2023 16:30:00 GMT</pubDate><guid isPermaLink="true">https://mattrickard.com/standard-causes-of-human-misjudgment-munger</guid></item><item><title>Proposal: an HTML element for spoilers</title><link>https://seirdy.one/posts/2023/11/12/spoiler-element/</link><description>An informal proposal for dedicated elements for spoiler tags in HTML: use-cases, syntax, semantics, recommended UA behavior, and comparisons with “details”</description><author>All content on Seirdy’s Home</author><pubDate>Wed, 29 Nov 2023 15:09:39 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/posts/2023/11/12/spoiler-element/</guid></item><item><title>Music updates (November 2023)</title><link>https://bobbiechen.com/blog/2023/11/29/music-updates-november-2023</link><description>&lt;p&gt;The latest and greatest in my life's music:&lt;/p&gt;&lt;p&gt;The Castaways added Jason (guitar), Angel (vocals), and Amolak (trumpet) to play a huge party at Alex's,
which must have had over 100 people.&lt;/p&gt;











































  

    
  
    

      

      
        &lt;figure class="
              sqs-block-image-figure
              intrinsic
            "&gt;
          
        
        

        
          
            
          
            
                
                
                
                
                
                
                
                &lt;img alt="" height="3024" src="https://images.squarespace-cdn.com/content/v1/595b129c72af65691b797b34/7a21215e-4f2b-4ed2-9ca6-cbd383093615/image-asset.jpeg?format=1000w" width="4032" /&gt;

            
          
        
          
        

        
      
        &lt;/figure&gt;
      

    
  


  


&lt;p&gt;And I attended two Afterglow Chorus concerts: one as a spectator, and one as a participant.
It was great, and also found a couple more jazz musicians within the group.
I'm hoping to introduce them to the Castaways group eventually,
but haven't had the chance yet.&lt;/p&gt;&lt;p&gt;These last couple months have been a little busier
(life, work, travel to Asia - and writing this in the airport on the way back),
so I'll leave this post here for now.&lt;/p&gt;&lt;p&gt;For a more personal life update,
I did get engaged to Yee Aun on September 16th -
music to my own ears.
With that in mind, I'll share a pair of songs for listening:&lt;/p&gt;&lt;h3 id="dimensions-live-joshua-milo"&gt;DIMENSIONS (Live) - Joshua Milo&lt;/h3&gt;&lt;p&gt;&lt;a href="https://open.spotify.com/track/6FA0LYGzNXuomxszI8YbBD?si=c711476764d14006"&gt;Spotify link&lt;/a&gt; | &lt;a href="https://joshuamilo.com/"&gt;Website&lt;/a&gt;&lt;/p&gt;&lt;p&gt;This song was playing as we drove into Carmel-by-the-Sea on the night before the proposal.
Energetic throughout with ups and downs, and a groove that you can't help but bob your head to -
I rarely drive, but this song's fusion feel made for perfect driving music.
Or as Yee Aun calls it, "Mario Kart music" (&lt;a href="https://www.youtube.com/watch?v=3rD-tdEFKlg"&gt;not a bad thing!&lt;/a&gt;).&lt;/p&gt;&lt;h3 id="it-only-takes-a-taste-drew-gehling-and-jessie-mueller"&gt;It Only Takes a Taste - Drew Gehling and Jessie Mueller&lt;/h3&gt;&lt;p&gt;&lt;a href="https://open.spotify.com/track/0jW4YGXcbUBsMjQa7aHuSf"&gt;Spotify link&lt;/a&gt; | &lt;a href="https://waitressthemusical.com/"&gt;Waitress the Musical&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Going back to the early days,
here's a song that is near and dear to my heart.
If I remember correctly, I originally heard Waitress from the Sara Bareilles demo album,
but then the version with Drew Gehling and Jessie Mueller breathed entirely new life into it.
I saw the musical when they toured in Pittsburgh with a couple friends,
and this song in particular is one I've always loved.&lt;/p&gt;&lt;p&gt;Post-graduation, I was thinking about making music again,
and this became the first song I covered and posted in several years:
&lt;a href="https://bobbiechen.com/blog/2020/10/10/it-only-takes-a-taste"&gt;my recording and blog post here&lt;/a&gt;.
At the time, I was just scratching the surface of getting to know Yee Aun better,
but as it goes, &lt;em&gt;it only takes a taste when it's something special&lt;/em&gt;.&lt;/p&gt;&lt;hr /&gt;&lt;p&gt;Other stuff to look forward to:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;Christmas music season :)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.couchtheband.com/tour"&gt;Couch concert in SF&lt;/a&gt; on December 1 - I'm going, see you there?&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;As always, recommendations, jamming, gig invites are always welcome!&lt;/p&gt;</description><author>Blog - Bobbie Chen</author><pubDate>Wed, 29 Nov 2023 15:01:19 GMT</pubDate><guid isPermaLink="true">https://bobbiechen.com/blog/2023/11/29/music-updates-november-2023</guid></item><item><title>The unknown future of software developers</title><link>https://ilearnt.com/blog/futuredevelopers/</link><description>&lt;p&gt;The role of the software developer is set for a major upheaval over the coming years.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Wed, 29 Nov 2023 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/futuredevelopers/</guid></item><item><title>Bouldering</title><link>https://www.swyx.io/bouldering</link><description>My first introduction to bouldering was in 2017, in DUMBO Boulders in Brooklyn Bridge Park (now closed):</description><author>swyx's site RSS Feed</author><pubDate>Wed, 29 Nov 2023 10:16:46 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/bouldering</guid></item><item><title>Seattle Bicycle Commuting Gear</title><link>https://zdwolfe.medium.com/seattle-bicycle-commuting-gear-3b41393c325f?source=rss-65d40a7904f------2</link><description>&lt;h3&gt;Seattle Bicycle Commuting&lt;/h3&gt;&lt;p&gt;&lt;em&gt;Seattle’s winter weather can be a bummer to bike through. Here’s a breakdown of the gear I use to make my daily 6-mile commute comfortable.&lt;/em&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*s6gh1Aohv9MNaYyGbhEwgg@2x.jpeg" /&gt;&lt;/figure&gt;&lt;p&gt;After the pandemic pushed me away from my work-walkable &lt;a href="https://zdwolfe.medium.com/renting-a-single-family-home-in-seattle-95bf190680b3"&gt;Seattle downtown apartment to a nearby neighborhood&lt;/a&gt;, I worked fully remote from my home office for about 18 months. As &lt;a href="https://www.cnn.com/2023/08/29/business/amazon-jobs-return-to-office/index.html"&gt;Return To Office enforcement began&lt;/a&gt;, I found myself needing to plan for a regular 6.6 mile commute.&lt;/p&gt;&lt;p&gt;After a few weeks of testing, I decided to commit to cycling as my main method of commuting for a few reasons:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Consistent, mentally-engaging morning cardio: &lt;/strong&gt;Needing to sweat and pay attention to get to work makes me not even realize I’m getting exercise. And at ~50 minutes and 500 calories/day, it’s a meaningful amount of cardio.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Quicker than bussing: &lt;/strong&gt;The bus is great, but very inconsistent especially in the evenings, adding ~20m/day to my total commute compared to bike or car on average, with outliers of &amp;gt; 40m.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Cheap: &lt;/strong&gt;Parking downtown is $18/day + $6/week in gas, and Uber was ~$28/day, so I’d spend somewhere around ~$3500/year to get to the office.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Low carbon footprint: &lt;/strong&gt;Using rough estimates, cycling uses about &lt;a href="https://www.bikeradar.com/features/long-reads/cycling-environmental-impact/"&gt;~21gCO2e/km, compared to driving’s 271gCO2e/km&lt;/a&gt;.&lt;/li&gt;&lt;/ol&gt;&lt;h3&gt;Issues&lt;/h3&gt;&lt;p&gt;After my test-runs, I found a few problems to solve, some more important than others:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Rain:&lt;/strong&gt; In Seattle, it rains an average of 156 days per year (42% of days), even though the total rainfall is low. After just one representative rainy commute, I was soaked, dirty, and carried around a wet backpack all day. I knew right away I wanted a few gear upgrades.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Backpack sweat:&lt;/strong&gt; Cycling with a backpack made me &lt;em&gt;sweaty&lt;/em&gt;. I really needed a shower at the office, increasing my total time spent getting from bed-to-desk-to-couch.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Big Laptop, Small Bag:&lt;/strong&gt; My MacBook Pro is chunky and doesn’t fit in my usual bag. So I was carrying around a 28L hiking pack all day because it’s all I had that worked.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Visibility:&lt;/strong&gt; Early mornings and late evenings mean low light. I’d like to not be squished, so I wanted some visibility improvements.&lt;/li&gt;&lt;/ol&gt;&lt;h3&gt;The Gear I Picked&lt;/h3&gt;&lt;p&gt;I spent a lot of time shopping around, reading reviews, agonizing, talking to cycling friends, etc, so I thought it might be valuable to summarize what I settled on for someone who also wants to commute via bike.&lt;/p&gt;&lt;h4&gt;Kona Dew Bike&lt;/h4&gt;&lt;p&gt;The &lt;a href="https://www.bikeexchange.com/a/hybrid-bikes/kona/wa/kennewick/dew-black/217985428?variant_id=123408224"&gt;Kona Dew&lt;/a&gt; is my steed. It’s simple, comfortable, inexpensive, and has many threaded mount-points on the frame to mount the gear described below. I find the relatively upright riding position to be easy on the back, and the cushy slick tires to be a good combination of comfortable riding and road traction.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gVxp_sngEHZTqB1O_TZwrQ.png" /&gt;&lt;/figure&gt;&lt;h4&gt;Portland Design Works Full Metal Fenders — 650 Beast&lt;/h4&gt;&lt;p&gt;I bought a set of &lt;a href="https://ridepdw.com/products/full-metal-fenders-650-beast"&gt;Portland Design Works Full Metal Fenders — 650 Beast&lt;/a&gt; from a local shop, &lt;a href="https://www.freerangecycles.com/"&gt;Free Range Cycles&lt;/a&gt;, justifying the costly hardware by installing them myself. My only complaint so far is the difficult install, requiring some after-market aluminum spacers to fit around my disc brakes, a hacksaw to shorten the fender stays, and an hour of wiggling and adjusting, but now I’m ready for those rainy days.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PS_MuqWq-15drI90EbfwrQ.png" /&gt;&lt;/figure&gt;&lt;h3&gt;Pannier and Bags&lt;/h3&gt;&lt;p&gt;To solve my “sweaty back” problem, I opted to ditch the backpack and get a bike-mounted pannier.&lt;/p&gt;&lt;p&gt;However, I use my bag in and around the office many days, occasionally moving around floors and buildings. I wanted a bike-mounted bag that I could use as an office bag, big enough to fit a laptop, clothes, and an extra layer, but not be giant and ungainly.&lt;/p&gt;&lt;h4&gt;Arkel Signature H — Urban Pannier&lt;/h4&gt;&lt;p&gt;The &lt;a href="https://arkel.ca/products/signature-h-urban-pannier?variant=43786946576633"&gt;Arkel Signature H Urban Pannier&lt;/a&gt; was my answer after looking at a dozen other options. Made with carbon-neutral waterproof X-Pac X11 fabric, it feels very tough and stayed perfectly dry after a shower test. Plus, no backpack means no sweaty back. This was recommended as the &lt;a href="https://www.nytimes.com/wirecutter/reviews/best-bike-rack-basket-panniers/"&gt;best pannier of 2023 by NY Times&lt;/a&gt;, and I can see why.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2pMMKuScMJV7DBCFnPdtYg.png" /&gt;&lt;/figure&gt;&lt;h4&gt;Topeak Explorer Rack&lt;/h4&gt;&lt;p&gt;I got a &lt;a href="https://www.topeak.com/global/en/product/1535-EXPLORER-(NON-DISC)-(W--SPRING)"&gt;Topeak Explorer Rack&lt;/a&gt;, also being rated best on a &lt;a href="https://www.nytimes.com/wirecutter/reviews/best-rear-bike-rack/"&gt;bike rack test by the NY Times&lt;/a&gt;. Even though I have disc brakes I opted for the non-disc option after looking at the dimensions and reading some reviews. Some disc brake designs have the brake caliper mounted on top of the chain stay, so lots of “disc brake” gear acommodate with spacers, increasing the footprint of the rack. To save some space, knowing my brake calipers wouldn’t interfere at all, I got the more narrow option. This was super simple to install, taking about 30m and typical bike Allen tools.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dLA6TUdAOj__pCVNXjvpAw.png" /&gt;&lt;/figure&gt;&lt;h3&gt;Tailfin Top Tube Pack — Flip&lt;/h3&gt;&lt;p&gt;I use a &lt;a href="https://www.tailfin.cc/product/small-packs/top-tube-packs/top-tube-pack-flip/?v=7516fd43adaa"&gt;Tailfin Top Tube Pack&lt;/a&gt; for items I regularly want access to while I’m riding, without dismounting: phone, keys, garage door opener, work badge, snacks.&lt;/p&gt;&lt;p&gt;This thing is great, and if you have to pick one position for a small bag, I highly recommend a top tube bag such as this.&lt;/p&gt;&lt;p&gt;Perfectly waterproof (passed a shower test), mounts securely to the top-tube bottle eyelets on my Kona Dew bike (shown below with optional straps I don’t use).&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/384/1*nDnfyMQhownPMegT72Rf6g@2x.jpeg" /&gt;&lt;/figure&gt;&lt;h4&gt;Ortlieb Saddle Bag Two&lt;/h4&gt;&lt;p&gt;I use the &lt;a href="https://www.ortlieb.com/en_us/saddle-bag-two+F9414"&gt;Ortlieb Saddle Bag Two&lt;/a&gt; for the things I need occasionally, but don’t need to-hand while riding: &lt;a href="https://evanw.com/q-a-what-do-you-carry-in-your-road-bike-saddle-bag/"&gt;some cash, contact lenses, tube and chain repair items, and gloves&lt;/a&gt;. These things I’m OK stopping to get out, as I need them only once in a while or never.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yKzO3FdNe9eq-fjYDHvkCw.png" /&gt;&lt;/figure&gt;&lt;h3&gt;Lights and Tools&lt;/h3&gt;&lt;h4&gt;Light &amp;amp; Motion VIS Pro 1000 Blacktop&lt;/h4&gt;&lt;p&gt;At the recommendation of a friend, I bought a &lt;a href="https://lightandmotion.com/products/vis-pro-1000-blacktop"&gt;Light &amp;amp; Motion VIS Pro 1000 Blacktop&lt;/a&gt;. The side-pointing lights make me feel a bit safer on those dim mornings and evenings, and might avoid a side-swiping.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/976/1*1-Z2fW4MgcHS9A3Qz_W9rA.png" /&gt;&lt;/figure&gt;&lt;h4&gt;OneUp Components EDC System&lt;/h4&gt;&lt;p&gt;After just a few weeks of biking to work, I saw more than one person repairing a flat on the side of the Lake Union trail. I decided I should get a pump and tools to fix a flat enough for me to limp home.&lt;/p&gt;&lt;p&gt;The &lt;a href="https://www.oneupcomponents.com/products/edc-pump"&gt;OneUp Components EDC Pump&lt;/a&gt; seemed perfect. It’s a pump that has a &lt;a href="https://www.oneupcomponents.com/products/edc-v2-tool"&gt;EDC Tool System&lt;/a&gt; and &lt;a href="https://www.oneupcomponents.com/products/edc-plug-kit"&gt;EDC Plug Kit&lt;/a&gt; that fit within the pump itself, all mounting to the frame of the bike, saving space in my bag.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7zI-x40wzf1dJYdClVwUfw.png" /&gt;&lt;/figure&gt;&lt;h3&gt;Wrapping Up&lt;/h3&gt;&lt;p&gt;Gear makes a big difference in comfort, especially riding in a rainy city like Seattle. I hope my cycling gear listed above gives you a good starting point for your commute.&lt;/p&gt;&lt;p&gt;-Zach&lt;/p&gt;&lt;img alt="" height="1" src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=3b41393c325f" width="1" /&gt;</description><author>Stories by Zach Wolfe on Medium</author><pubDate>Wed, 29 Nov 2023 04:49:00 GMT</pubDate><guid isPermaLink="true">https://zdwolfe.medium.com/seattle-bicycle-commuting-gear-3b41393c325f?source=rss-65d40a7904f------2</guid></item><item><title>Building your own bookmark launcher</title><link>https://xenodium.com/building-your-own-bookmark-launcher</link><description>&lt;p&gt;&lt;em&gt;✨&lt;a href="https://github.com/sponsors/xenodium"&gt;sponsor&lt;/a&gt;✨ this content&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I've been toying with the idea of managing browser bookmarks from &lt;a href="https://www.gnu.org/software/emacs/"&gt;you know where&lt;/a&gt;. Maybe dump a bunch of links into an org file and use that as a quick and dirty bookmark manager. We'll start with a flat list plus fuzzy searching and see how far that gets us.&lt;/p&gt;
&lt;p&gt;The org file would look a little something like this:&lt;/p&gt;
&lt;p&gt;::: captioned-content
::: caption
bookmarks.org
:::&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-org"&gt;My bookmarks
- [[https://lobste.rs/t/emacs][Emacs editor (Lobsters)]]
- [[https://emacs.stackexchange.com][Emacs Stack Exchange]]
- [[https://www.reddit.com/r/emacs][Emacs subreddit]]
- [[https://emacs.ch][Emacs.ch (Mastodon)]]
- [[https://www.emacswiki.org][EmacsWiki]]
- [[https://planet.emacslife.com/][Planet Emacslife]]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;Next we need fuzzy searching, but first let's write a little elisp to extract all links from the org file:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(require 'org-element)
(require 'seq)

(defun browser-bookmarks (org-file)
  &amp;quot;Return all links from ORG-FILE.&amp;quot;
  (with-temp-buffer
    (let (links)
      (insert-file-contents org-file)
      (org-mode)
      (org-element-map (org-element-parse-buffer) 'link
        (lambda (link)
          (let* ((raw-link (org-element-property :raw-link link))
                 (content (org-element-contents link))
                 (title (substring-no-properties (or (seq-first content) raw-link))))
            (push (concat title
                          &amp;quot;\n&amp;quot;
                          (propertize raw-link 'face 'whitespace-space)
                          &amp;quot;\n&amp;quot;)
                  links)))
        nil nil 'link)
      (seq-sort 'string-greaterp links))))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The snippet uses &lt;code&gt;org-element&lt;/code&gt; to iterate over links to collect/return them in a list. We join both the title and url, so searching can match either of these values. We also add a little formatting (new lines/face) to spiff things up.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(browser-bookmarks &amp;quot;/private/tmp/bookmarks.org&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can now feed our list to our preferred narrowing framework (ivy, helm, ido, vertico) and use it to quickly select a bookmark. In the past, I've &lt;a href="https://xenodium.com/emacs-utilities-for-your-os/"&gt;used the likes of ivy-read&lt;/a&gt; directly, though have since adopted the humble but mighty &lt;code&gt;completing-read&lt;/code&gt; which hooks up to any of the above frameworks.&lt;/p&gt;
&lt;p&gt;With that in mind, let's use &lt;code&gt;completing-read&lt;/code&gt; to make a selection and split the text to extract the corresponding URL. Feed it to &lt;code&gt;browse-url&lt;/code&gt;, and you got your preferred browser opening your bookmark.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(defun open-bookmark ()
  (interactive)
  (browse-url (seq-elt (split-string (completing-read &amp;quot;Open: &amp;quot; (browser-bookmarks &amp;quot;/private/tmp/bookmarks.org&amp;quot;)) &amp;quot;\n&amp;quot;) 1)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I remain a happy ivy user, so we can see its fuzzy searching in action.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/building-your-own-bookmark-launcher/emacs-bookmark.gif" /&gt;&lt;/p&gt;
&lt;p&gt;At this point, we now have our bookmark-launching Emacs utility. It's only an &lt;code&gt;M-x open-bookmark&lt;/code&gt; command away, but we want to make it accessible from anywhere in our operating system, in my case macOS.&lt;/p&gt;
&lt;p&gt;Let's enable launching from the command line, though before we do that, let's craft a dedicated frame for this purpose.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(defmacro present (&amp;amp;rest body)
  &amp;quot;Create a buffer with BUFFER-NAME and eval BODY in a basic frame.&amp;quot;
  (declare (indent 1) (debug t))
  `(let* ((buffer (get-buffer-create (generate-new-buffer-name &amp;quot;*present*&amp;quot;)))
          (frame (make-frame '((auto-raise . t)
                               (font . &amp;quot;Menlo 15&amp;quot;)
                               (top . 200)
                               (height . 20)
                               (width . 110)
                               (internal-border-width . 20)
                               (left . 0.33)
                               (left-fringe . 0)
                               (line-spacing . 3)
                               (menu-bar-lines . 0)
                               (minibuffer . only)
                               (right-fringe . 0)
                               (tool-bar-lines . 0)
                               (undecorated . t)
                               (unsplittable . t)
                               (vertical-scroll-bars . nil)))))
     (set-face-attribute 'ivy-current-match frame
                         :background &amp;quot;#2a2a2a&amp;quot;
                         :foreground 'unspecified)
     (select-frame frame)
     (select-frame-set-input-focus frame)
     (with-current-buffer buffer
       (condition-case nil
           (unwind-protect
               ,@body
             (delete-frame frame)
             (kill-buffer buffer))
         (quit (delete-frame frame)
               (kill-buffer buffer))))))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Most of the snippet styles our new frame and invokes the body parameter. While I don't typically resort to macros, we get a little syntatic sugar here, so we can invoke like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(defun present-open-bookmark-frame ()
  (present (browse-url (seq-elt (split-string (completing-read &amp;quot;Open: &amp;quot; (browser-bookmarks &amp;quot;/private/tmp/bookmarks.org&amp;quot;)) &amp;quot;\n&amp;quot;) 1))))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Wrapping our one-liner with the &lt;code&gt;present-open-bookmark-frame&lt;/code&gt; function enables us to easily invoke from the command line, with something like&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.bash"&gt;emacsclient -ne &amp;quot;(present-open-bookmark-frame)&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/building-your-own-bookmark-launcher/command.gif" /&gt;&lt;/p&gt;
&lt;p&gt;Now that we can easily invoke from the command line, we have the flexibility to summon from anywhere. We can even bind to a key shortcut, available anywhere (not just Emacs). I typically do this via &lt;a href="http://www.hammerspoon.org/"&gt;Hammerspoon&lt;/a&gt;, with some helpers, though there are likely simpler options out there.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-lua"&gt;function emacsExecute(activate, elisp)
   if activate then
      activateFirstOf({
            {
               bundleID=&amp;quot;org.gnu.Emacs&amp;quot;,
               name=&amp;quot;Emacs&amp;quot;
            }
      })
   end

   local socket, found = emacsSocketPath()
   if not found then
      hs.alert.show(&amp;quot;Could not get emacs socket path&amp;quot;)
      return &amp;quot;&amp;quot;, false
   end

   local output,success = hs.execute(&amp;quot;/opt/homebrew/bin/emacsclient -ne \&amp;quot;&amp;quot;..elisp..&amp;quot;\&amp;quot; -s &amp;quot;..socket)
   if not success then
      hs.alert.show(&amp;quot;Emacs did not execute: &amp;quot;..elisp)
      return &amp;quot;&amp;quot;, false
   end

   return output, success
end

function openBookmark()
   appRequestingEmacs = hs.application.frontmostApplication()
   emacsExecute(false, &amp;quot;(present-open-bookmark-frame)&amp;quot;)
   activateFirstOf({
         {
            bundleID=&amp;quot;org.gnu.Emacs&amp;quot;,
            name=&amp;quot;Emacs&amp;quot;
         }
   })
end

hs.hotkey.bind({&amp;quot;alt&amp;quot;}, &amp;quot;W&amp;quot;, openBookmark)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that, we have our Emacs-powered bookmark launcher, available from anywhere.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/building-your-own-bookmark-launcher/launcher.gif" /&gt;&lt;/p&gt;
&lt;p&gt;While we used our Emacs frame presenter to summon our universal bookmark launcher, we can likely the same mechanism for other purposes. Maybe a clipboard (kill ring) manager?&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/building-your-own-bookmark-launcher/kill-ring.png" /&gt;&lt;/p&gt;
&lt;p&gt;What would you use it for? Get in touch (&lt;a href="https://indieweb.social/@xenodium"&gt;Mastodon&lt;/a&gt; / &lt;a href="https://twitter.com/xenodium"&gt;Twitter&lt;/a&gt; / &lt;a href="https://www.reddit.com/user/xenodium"&gt;Reddit&lt;/a&gt; / &lt;a href="mailto:me__AT__xenodium.com"&gt;Email&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Enjoying this content? Find it useful? Consider ✨&lt;a href="https://github.com/sponsors/xenodium"&gt;sponsoring&lt;/a&gt;✨.&lt;/em&gt;&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Wed, 29 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/building-your-own-bookmark-launcher</guid></item><item><title>"MVP" is dead! Long live "FPC".</title><link>https://taylor.town/fpc</link><description>MVPs are for products. FPCs are for customers. Products aren't people.</description><author>taylor.town</author><pubDate>Wed, 29 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/fpc</guid></item><item><title>SSH Certificate Notes</title><link>https://skife.org/b4/ssh-certificates-notes/</link><description>&lt;p&gt;There are lots of tutorials out there, but I want to compile my notes so if I walk away from playing with them again for a few years, I can pick it up again!&lt;/p&gt;
&lt;h2 id="ssh-keys-for-different-things-are-all-the-same-modulo-chosen-algo-type"&gt;SSH Keys for different things are all the same (modulo chosen algo type)&lt;/h2&gt;
&lt;p&gt;User keys, host keys, CA keys -- they are all the same. Don't let &lt;code&gt;ssh-keygen&lt;/code&gt; docs, with all of its options and whatnot, convince you otherwise.&lt;/p&gt;
&lt;h2 id="use-authorizedprincipalsfile-to-manage-access"&gt;Use &lt;code&gt;AuthorizedPrincipalsFile&lt;/code&gt; to manage access&lt;/h2&gt;
&lt;p&gt;Don't go and create a local user account for everyone who may be accessing the host, just have a shared local user, probably even &lt;code&gt;root&lt;/code&gt;, where you differentiate who did what using the identity on the cert they authed with.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;AuthorizedPrincipalsFile&lt;/code&gt; is a file that lists the allowed principals for a given local user. Use &lt;em&gt;groups&lt;/em&gt; as principals, not individual users, and list the groups which may use the local user in the file for the given local user. The groups an actual user is in are then added to that user's cert.&lt;/p&gt;
&lt;pre class="giallo" style="color: #24292E; background-color: #FFFFFF;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;%u&lt;/code&gt; is the normal user placeholder, so &lt;code&gt;/etc/ssh/auth_principals/root&lt;/code&gt; would control the principals that may log in as &lt;code&gt;root&lt;/code&gt;. The file takes one principal per line. Copilot &lt;em&gt;really&lt;/em&gt; wants me to add that you can use wildcards in the file, but that idea is making me queasy. Principals as groups sounds better to me.&lt;/p&gt;
&lt;p&gt;The overall &lt;code&gt;sshd_config&lt;/code&gt; looks something like:&lt;/p&gt;
&lt;pre class="giallo" style="color: #24292E; background-color: #FFFFFF;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;Port {{ port }}&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Protocol 2&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;AcceptEnv LANG LC_*&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;LoginGraceTime 120&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;UsePAM no &lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;PasswordAuthentication no&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;IgnoreRhosts yes&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;PubkeyAuthentication yes&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;HostKey {{ path }}/ssh_host_ed25519_key&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;TrustedUserCAKeys {{ path }}/ca.pub&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;AuthorizedPrincipalsFile {{ path }}/auth_principals/%u&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="testing-with-sshd"&gt;Testing with sshd&lt;/h2&gt;
&lt;p&gt;You can run sshd as a normal user, as long as you only try to log in as that same user.&lt;/p&gt;
&lt;p&gt;Invocation looks like, &lt;code&gt;/opt/homebrew/sbin/sshd -d -D -f /tmp/my_ssh/sshd_config&lt;/code&gt;. This will start it without forking behavior, process a single login, and give us debug output.&lt;/p&gt;
&lt;p&gt;The sshd_config may need &lt;code&gt;StrictModes no&lt;/code&gt; depending on the directories in play and the permissions on them.&lt;/p&gt;
&lt;h2 id="various-invocations"&gt;Various invocations&lt;/h2&gt;
&lt;h3 id="ssh-with-weird-agent-socket"&gt;ssh with weird agent socket&lt;/h3&gt;
&lt;p&gt;Given I am doing this to test agent + cert muckery, invoking ssh to talk to the sshd mentioned above looks like:&lt;/p&gt;
&lt;pre class="giallo" style="color: #24292E; background-color: #FFFFFF;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #6F42C1;"&gt;ssh&lt;/span&gt;&lt;span style="color: #005CC5;"&gt; -v \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #005CC5;"&gt;    -o&lt;/span&gt;&lt;span style="color: #032F62;"&gt; UserKnownHostsFile=/dev/null&lt;/span&gt;&lt;span style="color: #005CC5;"&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #005CC5;"&gt;    -o&lt;/span&gt;&lt;span style="color: #032F62;"&gt; StrictHostKeyChecking=no&lt;/span&gt;&lt;span style="color: #005CC5;"&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #005CC5;"&gt;    -F&lt;/span&gt;&lt;span style="color: #032F62;"&gt; /dev/null&lt;/span&gt;&lt;span style="color: #005CC5;"&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #005CC5;"&gt;    -l&lt;/span&gt;&lt;span&gt; $USER&lt;/span&gt;&lt;span style="color: #005CC5;"&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #005CC5;"&gt;    -o&lt;/span&gt;&lt;span style="color: #032F62;"&gt; IdentityAgent=/path/to/agent.sock&lt;/span&gt;&lt;span style="color: #005CC5;"&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #005CC5;"&gt;    -p 2222 \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #032F62;"&gt;    localhost&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same, but for a local cert instead of agent (assuming &lt;code&gt;user_key&lt;/code&gt; is the private key, and has &lt;code&gt;user_key.pub&lt;/code&gt; and &lt;code&gt;user_key-cert.pub&lt;/code&gt; alongside it):&lt;/p&gt;
&lt;pre class="giallo" style="color: #24292E; background-color: #FFFFFF;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #6F42C1;"&gt;ssh&lt;/span&gt;&lt;span style="color: #005CC5;"&gt; -v \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #005CC5;"&gt;    -o&lt;/span&gt;&lt;span style="color: #032F62;"&gt; UserKnownHostsFile=/dev/null&lt;/span&gt;&lt;span style="color: #005CC5;"&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #005CC5;"&gt;    -o&lt;/span&gt;&lt;span style="color: #032F62;"&gt; StrictHostKeyChecking=no&lt;/span&gt;&lt;span style="color: #005CC5;"&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #005CC5;"&gt;    -F&lt;/span&gt;&lt;span style="color: #032F62;"&gt; /dev/null&lt;/span&gt;&lt;span style="color: #005CC5;"&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #005CC5;"&gt;    -l&lt;/span&gt;&lt;span&gt; $USER&lt;/span&gt;&lt;span style="color: #005CC5;"&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #005CC5;"&gt;    -i&lt;/span&gt;&lt;span style="color: #032F62;"&gt; path/to/user_key&lt;/span&gt;&lt;span style="color: #005CC5;"&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #005CC5;"&gt;    -p 2222 \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #032F62;"&gt;    localhost&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="given-a-ca-key-generate-a-user-cert-locally"&gt;Given a CA key, generate a user cert locally&lt;/h3&gt;
&lt;pre class="giallo" style="color: #24292E; background-color: #FFFFFF;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #6F42C1;"&gt;ssh-keygen&lt;/span&gt;&lt;span style="color: #005CC5;"&gt; -s&lt;/span&gt;&lt;span style="color: #032F62;"&gt;  path/to/ca&lt;/span&gt;&lt;span style="color: #005CC5;"&gt; -I&lt;/span&gt;&lt;span style="color: #032F62;"&gt; wobble&lt;/span&gt;&lt;span style="color: #005CC5;"&gt; -n&lt;/span&gt;&lt;span style="color: #032F62;"&gt; {{ principals }} .path/to/user_key&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add a comma seperated (no spaces) list of the principals (groups) for &lt;code&gt;{{ principals }}&lt;/code&gt; in that invocation (needs to align with the auth principals on sshd). The &lt;code&gt;-I&lt;/code&gt; is basically irrevalent for testing purposes, is just a cert identifier.&lt;/p&gt;</description><author>Brian's Waste of Time</author><pubDate>Wed, 29 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://skife.org/b4/ssh-certificates-notes/</guid></item><item><title>Creating a Static Media Review Site Backed by Airtable</title><link>https://xavd.id/blog/post/static-review-site-with-airtable/</link><description>My experience building a static site backed by an Airtable database, including some fairly complex TypeScript.&lt;br /&gt;&lt;br /&gt;&lt;a href="https://xavd.id/blog/post/static-review-site-with-airtable/"&gt;Read the whole thing&lt;/a&gt;.</description><author>The David Brownman Blog</author><pubDate>Wed, 29 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://xavd.id/blog/post/static-review-site-with-airtable/</guid></item><item><title>The Unreasonable Effectiveness of Monte Carlo</title><link>https://mattrickard.com/the-unreasonable-effectiveness-of-monte-carlo</link><description>Monte Carlo methods are used in almost every branch of science: to evaluate risk in finance, to generate realistic lighting and shadows in 3D graphics, to do re</description><author>Matt Rickard</author><pubDate>Tue, 28 Nov 2023 16:30:00 GMT</pubDate><guid isPermaLink="true">https://mattrickard.com/the-unreasonable-effectiveness-of-monte-carlo</guid></item><item><title>The cost of AI</title><link>https://ilearnt.com/blog/thecostofai/</link><description>&lt;p&gt;There is a rush at the moment to add AI to everything with a fear that if you don&amp;rsquo;t then you will be left behind.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Tue, 28 Nov 2023 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/thecostofai/</guid></item><item><title>Moving My Dotfiles to Nix</title><link>http://evantravers.com/articles/2023/11/28/moving-my-dotfiles-to-nix/</link><description>&lt;p&gt;&lt;img alt="" src="https://images.evantravers.com/articles/2023/11/wsl.png" /&gt;&lt;/p&gt;&lt;p&gt;I've been experimenting with &lt;a href="https://nixos.org/"&gt;Nix&lt;/a&gt;. I have a PC that is &lt;em&gt;way&lt;/em&gt; more powerful than my older Macbook. While experimenting with Ubuntu on WSL, and porting &lt;a href="https://github.com/evantravers/dotfiles/tree/before-nix"&gt;my existing dotfiles&lt;/a&gt; over, I remembered my friend Tom talking about Nix and NixOS and decided to give it a whirl.&lt;/p&gt;
&lt;p&gt;Two weeks later this article is being written on NixOS, in WSL, using my full dev environment. 🎉&lt;/p&gt;
&lt;h2&gt;&lt;a class="anchor" href="#nix" id="nix"&gt;&lt;/a&gt;Nix&lt;/h2&gt;
&lt;p&gt;The orange site and red site are filled with &lt;a href="https://shopify.engineering/what-is-nix"&gt;excellent articles about Nix&lt;/a&gt; and what it can do. For the sake of this article a brief overview.&lt;/p&gt;
&lt;p&gt;The word Nix points to three things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A functional language&lt;sup class="footnote-ref"&gt;&lt;a href="#fn-language" id="fnref-language"&gt;1&lt;/a&gt;&lt;/sup&gt; for describing configurations of software.&lt;/li&gt;
&lt;li&gt;A package manager using the Nix Language to handle installation and path management of software configurations.&lt;/li&gt;
&lt;li&gt;A *nix OS (NixOS) that can be configured using the Nix Language.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These three things provide a couple features very desirable to me:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using a strict configuration for my dev environment means that I can sync my setup between my work laptop, my personal laptop, and now this development WSL PC.&lt;/li&gt;
&lt;li&gt;NixOS being a configuration-first OS means I can summon a direct copy of it without having to spend hours setting it up. This is advantageous for virtualisation or if I want to move my WSL setup to a dual-boot someday.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a class="anchor" href="#macos-or-wsl" id="macos-or-wsl"&gt;&lt;/a&gt;MacOS or WSL&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://wezfurlong.org/wezterm/"&gt;Wezterm&lt;/a&gt; works on both my &amp;quot;host&amp;quot; OS. I just run the executable and put my wezterm.lua wherever it needs to be.&lt;/p&gt;
&lt;p&gt;I do have &lt;a href="https://github.com/evantravers/dotfiles/blob/010f1ab583427019b00d4a145dfc3aa435c3df3d/home.nix#L41-L46"&gt;a wild little script&lt;/a&gt; that runs and installs the wezterm terminfo file. &lt;em&gt;Very&lt;/em&gt; un-nix.&lt;/p&gt;
&lt;p&gt;On my Macs I can't run NixOS but I can at least &lt;a href="https://nixos.org/manual/nix/stable/installation/installation.html"&gt;install the Nix Package manager&lt;/a&gt;. There's a relatively complicated install process involving a separate volume and mounting… but the installer is pretty easy to run.&lt;/p&gt;
&lt;p&gt;On the Windows PC I used the &lt;a href="https://github.com/nix-community/NixOS-WSL"&gt;WSL2 instructions&lt;/a&gt; provided by NixOS. I also added&lt;/p&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-lua" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;default_domain&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;'WSL:NixOS'&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to my Windows &lt;code&gt;.wezterm.lua&lt;/code&gt; so that Wezterm will connect to WSL on launch.&lt;/p&gt;
&lt;h2&gt;&lt;a class="anchor" href="#home-manager" id="home-manager"&gt;&lt;/a&gt;Home Manager&lt;/h2&gt;
&lt;p&gt;After NixOS or Nix Package Manager have been installed we can install &lt;a href="https://nix-community.github.io/home-manager/"&gt;Home Manager&lt;/a&gt; manage our dev environment.&lt;/p&gt;
&lt;p&gt;It's specifically built for this problem and replaces what I was doing with my &lt;code&gt;Brewfile&lt;/code&gt;, &lt;code&gt;Makefile&lt;/code&gt;, and &lt;code&gt;stow&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I took it slowly over a couple of weeks. At first my home-manager consisted of &lt;a href="https://github.com/evantravers/dotfiles/blob/a12d053cb440a0e73859c8011ba60958f0bec112/home.nix"&gt;installing some packages and moving my existing dotfiles to the right place&lt;/a&gt;. Very un-Nix-like but it totally worked.&lt;/p&gt;
&lt;p&gt;As I explored the options appendix and debugged problems I wound up &lt;a href="https://github.com/evantravers/dotfiles/blob/010f1ab583427019b00d4a145dfc3aa435c3df3d/home.nix"&gt;here today&lt;/a&gt;. I chose not to put &lt;em&gt;everything&lt;/em&gt; into the Nix configuration and keep some of the configuration in files for backwards compatibility. In a pinch you could &lt;code&gt;stow&lt;/code&gt; or &lt;code&gt;ln&lt;/code&gt; those folders/files in your home just like I was before.&lt;/p&gt;
&lt;p&gt;Let's explore some of the bumps in the road.&lt;/p&gt;
&lt;h2&gt;&lt;a class="anchor" href="#neovim" id="neovim"&gt;&lt;/a&gt;Neovim&lt;/h2&gt;
&lt;p&gt;I eventually moved to using the &lt;code&gt;programs.neovim&lt;/code&gt; configuration provided by Home Manager. I really wrestled with getting everything setup correctly. I think part of the problem was existing executables and configuration files still in path on OSX that was confusing the universe but in the end there were two big problems:&lt;/p&gt;
&lt;h3&gt;&lt;a class="anchor" href="#treesitter-wants-to-compile" id="treesitter-wants-to-compile"&gt;&lt;/a&gt;Treesitter wants to compile&lt;/h3&gt;
&lt;p&gt;Treesitter is awesome. But it wants to run scripts and compile things. Nix's store doesn't love that… it wants each folder to be idempotent and sets them to Read Only.&lt;/p&gt;
&lt;p&gt;I added &lt;a href="https://github.com/evantravers/dotfiles/blob/010f1ab583427019b00d4a145dfc3aa435c3df3d/home.nix#L135-L145"&gt;pkgs.vimPlugins.nvim-treesitter&lt;/a&gt; and eventually found the sequence of settings to make it work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;add &lt;code&gt;config&lt;/code&gt; block to configure highlight and indent on, but I got errors… set &lt;code&gt;type = &amp;quot;lua&amp;quot;;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;use &lt;code&gt;withAllGrammars&lt;/code&gt; so that it automatically gets all of them.&lt;/li&gt;
&lt;li&gt;everything is installed but not loading, use &lt;code&gt;packadd&lt;/code&gt; to force it to load the package.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All this wasn't working and I realized that…&lt;/p&gt;
&lt;h3&gt;&lt;a class="anchor" href="#lazynvim-takes-over-rtp" id="lazynvim-takes-over-rtp"&gt;&lt;/a&gt;Lazy.nvim takes over RTP&lt;/h3&gt;
&lt;p&gt;Turns out my beloved &lt;a href="https://github.com/folke/lazy.nvim"&gt;Lazy.nvim&lt;/a&gt; completely takes over the &lt;a href="https://vimhelp.org/options.txt.html#%27runtimepath%27"&gt;RTP&lt;/a&gt;. This means that plugins loaded manually (or by Nix!) can't be found by neovim. Someone else &lt;a href="https://discourse.nixos.org/t/neovim-cant-load-plugins/31189/6"&gt;pointed out there was a setting&lt;/a&gt;, so &lt;a href="https://github.com/evantravers/dotfiles/commit/6bd0928664fa4e0eb75ec18f92424256e1d6a723"&gt;I was able to disable this&lt;/a&gt; and suddenly neovim could see plugins loaded by Nix.&lt;/p&gt;
&lt;p&gt;These may be bad and wrong, if so… shoot me an email?&lt;/p&gt;
&lt;h2&gt;&lt;a class="anchor" href="#tmux" id="tmux"&gt;&lt;/a&gt;Tmux&lt;/h2&gt;
&lt;p&gt;Having learned from my Neovim mistakes Tmux was a bit easier. Like Treesitter, &lt;a href="https://github.com/tmux-plugins/tpm"&gt;TPM&lt;/a&gt; wants to modify it's read-only folders. I moved some settings into the configuration, install the plugins using Home Manager, and use my existing config for theming and style.&lt;/p&gt;
&lt;h2&gt;&lt;a class="anchor" href="#fish" id="fish"&gt;&lt;/a&gt;Fish&lt;/h2&gt;
&lt;p&gt;I've been using a stripped down Fish install for a while now. I almost wonder if I should use it at all. I basically just install it and use Home Manager to enable Starship.&lt;/p&gt;
&lt;p&gt;I still am using &lt;code&gt;asdf&lt;/code&gt; in some places. It'll likely phase out as I get better at flakes.&lt;/p&gt;
&lt;h2&gt;&lt;a class="anchor" href="#" id=""&gt;&lt;/a&gt;…&lt;/h2&gt;
&lt;p&gt;I'm starting to play with &lt;a href="https://devenv.sh/"&gt;devenv.sh&lt;/a&gt; and flakes to create my development environments. At the moment I'm still making heavy use of &lt;code&gt;asdf&lt;/code&gt; to handle ruby/js package management. I am wrestling with how to manage gems that want to build native extensions. I've been warned by kind friends that it's enough to drive one crazy… but I've got the bandwidth presently for the challenge.&lt;/p&gt;
&lt;p&gt;I like the idea of only enabling LSP executables and maybe even entire Neovim configurations per project using flakes and overlays. I've got a really simplistic version of this working using &lt;code&gt;direnv&lt;/code&gt; at the moment.&lt;/p&gt;
&lt;p&gt;All this &lt;em&gt;very much&lt;/em&gt; falls under &lt;a href="https://xkcd.com/1319/"&gt;XKCD's warning of automation addiction&lt;/a&gt;. For me it's worthwhile to learn a little bit about Nix and to face the puzzle of *nix and pathing problems.&lt;/p&gt;
&lt;p&gt;I really love that I can run &lt;code&gt;home-manager switch&lt;/code&gt; and know that everything I need to start working gets automatically put in the right place. That's pretty hard to beat.&lt;/p&gt;
&lt;p&gt;Now I suppose it's actually time to read Nix's actual documentation. :P&lt;/p&gt;
&lt;section class="footnotes"&gt;
&lt;ol&gt;
&lt;li id="fn-language"&gt;
&lt;p&gt;Nix &lt;em&gt;loves&lt;/em&gt; the trailing &lt;code&gt;;&lt;/code&gt;. Absolutely required. Really hard for my ruby/JS brain. &lt;a class="footnote-backref" href="#fnref-language"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</description><author>trv.rs</author><pubDate>Tue, 28 Nov 2023 12:15:00 GMT</pubDate><guid isPermaLink="true">http://evantravers.com/articles/2023/11/28/moving-my-dotfiles-to-nix/</guid></item><item><title>Wish</title><link>https://blog.yiningkarlli.com/2023/11/wish.html</link><description>&lt;div style="display: none;"&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;/h2&gt;

&lt;div class="tableofcontents"&gt;
    &lt;div class="tableofcontents-row"&gt;
        &lt;div class="tableofcontents-column3"&gt;
            &lt;div class="tableofcontents-content"&gt;
                1. &lt;a href="/2023/11/wish.html#2023-11-28-work"&gt;Making Wish&lt;/a&gt;&lt;br /&gt;
                2. &lt;a href="/2023/11/wish.html#2023-11-28-frames"&gt;Frames from the Film&lt;/a&gt;&lt;br /&gt;
                3. &lt;a href="/2023/11/wish.html#2023-11-28-references"&gt;References&lt;/a&gt;&lt;br /&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class="tableofcontents-column3"&gt;
        &lt;/div&gt;
        &lt;div class="tableofcontents-column3"&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="2023-11-28-work"&gt;&lt;/div&gt;

&lt;p&gt;Disney Animation’s fall 2023 release is &lt;em&gt;Wish&lt;/em&gt;, which is the studio’s 62nd animated feature and also the studio’s film celebrating the 100th anniversary of Disney Animation and The Walt Disney Company.
&lt;em&gt;Wish&lt;/em&gt; is a brand new story but is also steeped in the past century of Disney storytelling; while Asha and Star and Valentino’s adventure is a new musical story, the themes and setting &lt;em&gt;Wish&lt;/em&gt; draws upon are timeless and classic.
As part of this theme of modern Disney with throwback elements, &lt;em&gt;Wish&lt;/em&gt; also has a unique beautiful visual style that combines the latest of our computer graphics animation with a classic watercolor style.
Creating this style presented an interesting set of new challenges for our artists, TDs, engineers, and for Disney’s Hyperion Renderer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_001.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_001.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At Disney Animation, every one of our films is a new opportunity for us to push our filmmaking art and technology forward.
On most films this advancement takes place across many different aspects of the film, but on &lt;em&gt;Wish&lt;/em&gt;, there is one obvious challenge that stood out above everything else: the film’s visual style.
Of course we still made large improvements in other areas, such as major pipeline optimizations &lt;a href="https://doi.org/10.1145/3641233.3664335"&gt;[Li et al. 2024a]&lt;/a&gt;, but on &lt;em&gt;Wish&lt;/em&gt; a large proportion of technology development was focused on achieving the target visual style.
One could be forgiven for thinking that stylization is mostly a rendering problem, but on &lt;em&gt;Wish&lt;/em&gt; it really was a challenge that reached into every department and every corner or our production process.
Stylization on &lt;em&gt;Wish&lt;/em&gt; meant stylization in modeling, lookdev, animation, effects, lighting, everything else in between, and even new pipeline challenges!&lt;/p&gt;

&lt;p&gt;The decision to give &lt;em&gt;Wish&lt;/em&gt; a unique style came from pretty much the very beginning of the project; the studio wanted to do something special for the 100th anniversary film to tie together our modern way of making animated films with the studio’s rich hand-drawn heritage.
The look of &lt;em&gt;Wish&lt;/em&gt; is especially influenced by early 20th century Disney traditional watercolor animation, with &lt;em&gt;Snow White and the Seven Dwarfs&lt;/em&gt; (1937) and &lt;em&gt;Sleeping Beauty&lt;/em&gt; (1959) being the largest guideposts.
This influence extends all the way to the very shape of the film, so to speak- &lt;em&gt;Wish&lt;/em&gt; is the first CG film that Disney Animation has made in the ultrawide 2.55:1 Cinemascope-style aspect ratio, matching the aspect ratio that was used on &lt;em&gt;Sleeping Beauty&lt;/em&gt; and &lt;em&gt;Lady and the Tramp&lt;/em&gt; (1955).
This aspect ratio choice meant that stylization on Wish even impacted layout, since they had to think about how to frame for such an ultrawide image!&lt;/p&gt;

&lt;p&gt;Disney Animation has a long history of stylizing 3D CG to resemble and fit in with hand-drawn animation, going all the way back to the studio’s traditional hand-drawn era &lt;a href="https://doi.org/10.1145/3665320.367099"&gt;[Meier 1996&lt;/a&gt;, &lt;a href="https://doi.org/10.1145/311625.312010"&gt;Daniels 1999&lt;/a&gt;, &lt;a href="https://history.siggraph.org/learning/hybrid-ink-line-rendering-in-a-production-environment-by-tamstorf-montoya-vozmediano-teece-and-dalton/"&gt;Tamstorf et al. 2001&lt;/a&gt;, &lt;a href="https://doi.org/10.1145/1242073.1242173"&gt;Odermatt and Springfield 2002&lt;/a&gt;, &lt;a href="https://doi.org/10.1145/965400.965479"&gt;Teece 2003]&lt;/a&gt;.
In the 3D CG era, Disney Animation has continually experimented with stylizing CG as well with a number of different approaches.
&lt;em&gt;Paperman&lt;/em&gt; focused on integrating 2D linework with 3D rendered characters &lt;a href="https://history.siggraph.org/learning/the-art-and-science-behind-walt-disney-animation-studios-paperman-by-kahrs-osborne-sathe-turley-and-whited/"&gt;[Kahrs et al. 2012 &lt;/a&gt;,&lt;a href="https://doi.org/10.1145/2343045.2343071"&gt;Whited et al. 2012]&lt;/a&gt;, while &lt;em&gt;Feast&lt;/em&gt; experimented with driving 2D lighting entirely in compositing on top of flat unlit/unshaded 3D renders &lt;a href="https://history.siggraph.org/learning/feast-a-look-at-walt-disney-animation-studios-newest-short-by-osborne-and-staub/"&gt;[Osborne and Staub 2014]&lt;/a&gt;.
The studio’s recent &lt;em&gt;Short Circuit&lt;/em&gt; experimental shorts program had many shorts that experimented with a variety of different stylized looks, ranging from Chinese ink brush watercolors to graphic 2D illustrations to stop motion and wood carved looks &lt;a href="https://doi.org/10.1145/3388767.3409267"&gt;[Newfield and Staub 2020]&lt;/a&gt;.
Disney Research has also worked closely with Disney Animation in the past decade plus to develop various experimental stylization techniques &lt;a href="https://doi.org/10.1145/2010324.1964923"&gt;[Schmid et al. 2011&lt;/a&gt;, &lt;a href="https://doi.org/10.1145/2591011"&gt;Sýkora et al. 2014]&lt;/a&gt;.
Both &lt;em&gt;Strange World&lt;/em&gt; and &lt;em&gt;Raya and the Last Dragon&lt;/em&gt; had small amounts of stylized sequences as well, with &lt;em&gt;Strange World&lt;/em&gt; doing a 1950s pulp scifi comic book look and &lt;em&gt;Raya and the Last Dragon&lt;/em&gt; doing a more graphic digital mixed media sort of look.
Most recently, the short &lt;em&gt;&lt;a href="/2022/03/far-from-the-tree.html"&gt;Far from the Tree&lt;/a&gt;&lt;/em&gt; utilized a look with cel-shaded characters on watercolor backgrounds.
All of these were animated using our standard 3D pipeline, with much of the look being built using a combination of render passes from the renderer (Hyperion for everything except &lt;em&gt;Paperman&lt;/em&gt;, which preceded Hyperion’s existence by a few years), various tricks in lighting, and a lot of work in compositing; how much of each was used varied widely per show and per target style.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Wish&lt;/em&gt; builds upon all of these predecessors.
&lt;em&gt;Wish&lt;/em&gt;’s stylization system is a vastly expanded version of what was used on &lt;em&gt;Far from the Tree&lt;/em&gt;, which in turn was built on top of everything that was developed for &lt;em&gt;Short Circuit&lt;/em&gt;, which in turn drew upon lessons from both &lt;em&gt;Feast&lt;/em&gt; and &lt;em&gt;Paperman&lt;/em&gt;.
One of the biggest challenges came from having to scale up a stylization pipeline from a short film to a full feature length project, while also trying to hit a new target style.
Early tests on the show were able to reproduce in CG the target visdev paintings essentially exactly, but through entirely ad-hoc and mostly manual approaches, which we then had to systematically take apart and figure out how to apply to the whole movie.
My wife, Harmony Li, was an Associate Technical Supervisor on the show and (among a ton of other things) oversaw the development of the entire technical backend that was built out to support stylization on &lt;em&gt;Wish&lt;/em&gt; &lt;a href="https://doi.org/10.1145/3641233.3664336"&gt;[Li et al. 2024b]&lt;/a&gt;; as a member of the rendering team, I got to work with her on this, which was great fun!
Meanwhile, much of the development for the actual techniques used was led primarily by lighting and lookdev artists.&lt;/p&gt;

&lt;p&gt;An early breakthrough in achieving &lt;em&gt;Wish&lt;/em&gt;’s style was finding that combining Kuwahara filters&lt;sup id="2023-11-28-kuwahara-footnote-1-backlink"&gt;&lt;a href="#2023-11-28-kuwahara-footnote-1"&gt;1&lt;/a&gt;&lt;/sup&gt; &lt;a href="https://doi.org/10.1007/978-1-4684-0769-3_13"&gt;[Kuwahara et al. 1976]&lt;/a&gt;  with linework generated from the renderer created a convincing starting point for a line-on-watercolor look that could use the renderer’s physical lighting as a starting point, instead of needing to construct stylized lighting entirely from scratch in comp on top of flat-shaded unlit renders.
To help really tie together the watercolor look, early tests put the entire image on a watercolor paper texture background, but once we tested the watercolor paper texture background in motion, some issues became apparent.
With just a static watercolor paper texture background, the illusion of motion broke as animation looked like it was “swimming” through the texture, but simply texturing everything with watercolor textures in 3D space looked downright bizarre since it looked less like the frame was a watercolor painting and more like all of the characters and the environment were made out of paper.
To solve this problem, the Hyperion team invented a new dynamic screen space texture technique &lt;a href="https://doi.org/10.1145/3641233.3664321"&gt;[Burley et al. 2024]&lt;/a&gt; where the renderer would project screen space textures onto 3D surfaces while tracking motion vectors.
The result is that &lt;em&gt;Wish&lt;/em&gt;’s watercolor backgrounds look like just a flat sheet of watercolor paper when still, but under motion convincingly move with the characters while neither looking like they’re actually in 3D space nor looking disconnected from motion.&lt;/p&gt;

&lt;p&gt;One interesting question I worked on for &lt;em&gt;Wish&lt;/em&gt; was making Hyperion robustly handle shading normals that are really dramatically disconnected from the underlying “physically correct” geometric normal.
Extreme bending of normals was used extensively on &lt;em&gt;Wish&lt;/em&gt; to simplify shapes and art-direct lighting detail and shadows.
In a normal physically based path traced render, shading normals coming from bump mapping and normal mapping usually have at least some relationship with the underlying true geometric normal, meaning that the ways shading normals modify light transport are relatively constrained to a plausible range.
However, on &lt;em&gt;Wish&lt;/em&gt;, extreme shading normals were used for things like simplifying the lighting on an entire complex tree canopy to match what the lighting would be on a simple sphere.
Making Hyperion handle these cases both from an authoring perspective and making Hyperion’s light transport robust against these cases took some work!&lt;/p&gt;

&lt;p&gt;There were actually also some more traditional physically based rendering problems to solve on &lt;em&gt;Wish&lt;/em&gt; too, which one might not necessarily expect for such a stylized film.
For some of Magnifico’s magic, the art-direction called for a sort of prismatic look where white light would get split into different colors.
We decided to try to achieve this effect through physically based shading, since the starting point for &lt;em&gt;Wish&lt;/em&gt;’s entire stylization pipeline was renders with physical lighting in order to provide consistency.
To achieve this effect through physically based shading, I extended the Disney BSDF with spectral dispersion support (retrofitting a spectral effect into a non-spectral renderer was a fun challenge worth discussing on its own someday).
Once our lookdev artists had access to dispersion within the Disney BSDF, it was fun seeing all of the other places where they started sprinkling the effect in, such as in various glass objects.&lt;/p&gt;

&lt;p&gt;Stylization on &lt;em&gt;Wish&lt;/em&gt; didn’t just mean new renderer effects and lighting and compositing work; in order to make characters read correctly in a watercolor look, stylization had to be incorporated into all of the characters at a geometric and design level as well, and had to be incorporated into animation and simulation.
As an example: a core story device in &lt;em&gt;Wish&lt;/em&gt; is the collective wishes of Rosas, which take the form of orbs containing entire small worlds set inside of swirling volumetrics.
Creating these wishes required clever pipeline solutions to embed entire stylized animated scenes inside of the orbs in 3D space, which was used instead of a usual compositing-based insert-shot workflow or the teleport-based solution &lt;a href="https://doi.org/10.1145/3532836.3536269"&gt;[Butler et al. 2022]&lt;/a&gt; used on Encanto; this approach was taken in order to provide animators with the ability to sync and see fully combined shots interactively and to simplify the rendering setup needed for stylization in lighting and compositing &lt;a href="https://doi.org/10.1145/3641233.3664337"&gt;[Karanam et al. 2024]&lt;/a&gt;.
On top of creating the individual wishes, huge numbers of wishes then had to be choreographed into tight, closely synchronized formations to meet the art-direction and shape language of the songs they were a part of, which required developing new crowd rigs and animation controls.
The rendering aspect of the wishes was in a lot of ways actually the easy part!
Each wish was also an internally emissive object, so when thousands of wishes are massed together in key sequences in the movie, we initially had some concerns about efficiently rendering all of the wishes, but our long-standing cache points many-light selection strategy &lt;a href="https://doi.org/10.1145/3665320.3670993"&gt;[Li et al. 2024c]&lt;/a&gt; proved to be more than capable for the task.&lt;/p&gt;

&lt;p&gt;Another example of stylization far upstream of lighting is in the project’s entire approach to character stylization.
Character hair and fur grooms required a different approach from our usual process; normally in more photoreal Disney Animation films, hair and fur grooms are built to be highly detailed to support the rich detailed look of the film, but &lt;em&gt;Wish&lt;/em&gt;’s watercolor style meant using a more simplified and graphic shape language across the board, where detail is traded off for a stronger focus on silhouette and overall massing.
Hair and fur grooms had to be adjusted to match, and hair and fur simulation had to be adjusted to keep art-directed shapes intact instead of operating on a more individual strand-based level &lt;a href="https://doi.org/10.1145/3641233.3664343"&gt;[Kaur and Stratton 2024]&lt;/a&gt;.
Asha’s braids, with their North African inspired long box braids, required additional attention to create and simulate &lt;a href="https://doi.org/10.1145/3641233.3664338"&gt;[Kaur et al. 2024]&lt;/a&gt;.
The braids themselves were already a major technical challenge; even using our state-of-the-art in-house grooming system Tonic &lt;a href="https://www.disneyanimation.com/publications/disneys-hair-pipeline-crafting-hair-styles-from-design-to-motion/"&gt;[Simmons and Whited 2014]&lt;/a&gt;, the braids still required a final groom two orders of magnitude more complex than our average groom.
Once Asha’s groom was figured out, her hair then had to also be put through the same stylized simulation setup mentioned earlier, with extensive 2D drawovers being used to art-direct simulations.
Character animation then also had to take into account the fact that &lt;em&gt;Wish&lt;/em&gt; does not have motion blur and how that impacts how viewers perceive character performances.&lt;/p&gt;

&lt;p&gt;Speaking of 2D drawovers, one particularly interesting use of 2D drawings to art-direct stylization on &lt;em&gt;Wish&lt;/em&gt; is in Magnifico’s magic and in various effects like flames and torches.
Normal volumetric effects created from simulations tend to be highly physical and detailed, but &lt;em&gt;Wish&lt;/em&gt;’s style called for these effects to harken back to the much more graphic shape language of magic effects from Disney Animation’s hand-drawn era.
To do this, our effects artists built on top of the neural volume style transfer work from &lt;em&gt;Raya and the Last Dragon&lt;/em&gt; &lt;a href="https://doi.org/10.1145/3450623.3464652"&gt;[Navarro and Rice 2021]&lt;/a&gt; and &lt;em&gt;Strange World&lt;/em&gt; &lt;a href="https://doi.org/10.1145/3587421.3595448"&gt;[Navarro 2023]&lt;/a&gt; to develop a new system where effects animation would begin with hand-drawn 2D elements, which were then projected and extruded into the 3D space to provide a guide for neural volume style transfer on top of volume simulations &lt;a href="https://doi.org/10.1145/3641233.3664340"&gt;[Tollec and Navarro 2024]&lt;/a&gt;.
The result is that &lt;em&gt;Wish&lt;/em&gt;’s volumetric effects combine the movement and interactions of physical simulations while retaining the shape and style of traditional hand-drawn effects.&lt;/p&gt;

&lt;p&gt;Everything I’ve written about here is just what I was familiar with on this film; vastly more work went into every single frame of &lt;em&gt;Wish&lt;/em&gt; than even I know.
The final look of &lt;em&gt;Wish&lt;/em&gt; is something that I think really is unique and beautiful.
&lt;em&gt;Wish&lt;/em&gt;’s 3D watercolor look speaks to the entire history of Disney Animation and simultaneously roots itself in the studio’s rich traditional hand-drawn legacy while also exemplifying the studio’s long history of innovating and driving filmmaking craft forward.
Walt Disney never stopped seeking to innovate in animation, and 100 years after he founded the studio, the animation studio that carries his name today continues to embody that same bold spirit on every new film.
As someone who’s a lifelong fan and student of animation, I feel incredibly humbled and fortunate to get to contribute towards that legacy every day.
&lt;em&gt;Wish&lt;/em&gt; is also accompanied by the short &lt;em&gt;&lt;a href="/2024/03/once-upon-a-studio.html"&gt;Once Upon a Studio&lt;/a&gt;&lt;/em&gt;, which is a wonderful tribute to 100 years of Disney Animation characters.&lt;/p&gt;

&lt;p&gt;Below are some frames from &lt;em&gt;Wish&lt;/em&gt;, pulled from the Blu-ray and presented in no particular order. As always, I’d highly recommend seeing &lt;em&gt;Wish&lt;/em&gt; on the biggest screen you can find!&lt;/p&gt;

&lt;div id="2023-11-28-frames"&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_002.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_002.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_003.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_003.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_004.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_004.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_005.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_005.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_006.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_006.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_007.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_007.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_008.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_008.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_009.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_009.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_010.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_010.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_011.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_011.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_012.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_012.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_013.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_013.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_014.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_014.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_015.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_015.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_016.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_016.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_017.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_017.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_018.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_018.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_019.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_019.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_049.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_049.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_037.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_037.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_080.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_080.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_021.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_021.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_062.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_062.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_052.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_052.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_046.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_046.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_032.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_032.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_090.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_090.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_094.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_094.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_067.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_067.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_061.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_061.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_027.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_027.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_068.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_068.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_071.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_071.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_093.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_093.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_058.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_058.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_029.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_029.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_064.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_064.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_082.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_082.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_098.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_098.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_039.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_039.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_059.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_059.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_040.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_040.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_026.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_026.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_047.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_047.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_083.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_083.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_099.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_099.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_089.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_089.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_069.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_069.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_036.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_036.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_033.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_033.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_097.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_097.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_066.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_066.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_102.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_102.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_088.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_088.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_051.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_051.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_022.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_022.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_074.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_074.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_100.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_100.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_081.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_081.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_079.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_079.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_048.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_048.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_070.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_070.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_023.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_023.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_091.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_091.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_035.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_035.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_043.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_043.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_063.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_063.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_078.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_078.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_075.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_075.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_024.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_024.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_053.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_053.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_038.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_038.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_028.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_028.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_034.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_034.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_087.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_087.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_085.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_085.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_095.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_095.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_031.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_031.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_025.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_025.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_054.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_054.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_045.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_045.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_092.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_092.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_041.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_041.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_073.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_073.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_042.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_042.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_030.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_030.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_050.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_050.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_057.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_057.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_072.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_072.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_065.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_065.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_077.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_077.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_084.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_084.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_056.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_056.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_044.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_044.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_101.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_101.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_076.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_076.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_055.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_055.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_060.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_060.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_086.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_086.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_096.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_096.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_020.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_020.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here are two credits frames from &lt;em&gt;Wish&lt;/em&gt;; the first is the fancy hero-credit card for my wife Harmony Li and her fellow Associate Technical Supervisors, and the second is for the Hyperion team, along with several of the Hyperion’s sister technology teams that all support lighting and lookdev.
Also, &lt;em&gt;Wish&lt;/em&gt; has a lovely post-credits scene that I’d encourage sticking around for!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_credits1.png"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_credits1.png" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_credits2.png"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2023/Nov/wish/WISH_credits2.png" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All images in this post are courtesy of and the property of Walt Disney Animation Studios.&lt;/p&gt;

&lt;div id="2023-11-28-references"&gt;&lt;/div&gt;
&lt;h2 id="references"&gt;References&lt;/h2&gt;

&lt;p&gt;Brent Burley, Brian Green, and Daniel Teece. 2024. &lt;a href="https://doi.org/10.1145/3641233.3664321"&gt;Dynamic Screen Space Textures for Coherent Stylization&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2024 Talks&lt;/em&gt;. Article 50.&lt;/p&gt;

&lt;p&gt;Corey Butler, Brent Burley, Wei-Feng Wayne Huang, Yining Karl Li, and Benjamin Huang. 2022. &lt;a href="https://doi.org/10.1145/3532836.3536269"&gt;“Encanto” - Let’s Talk About Bruno’s Visions&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2022 Talks&lt;/em&gt;. Article 8.&lt;/p&gt;

&lt;p&gt;Eric Daniels. 1999. &lt;a href="https://doi.org/10.1145/311625.312010"&gt;Deep Canvas in Disney’s Tarzan&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 1999 Sketches &amp;amp; Applications&lt;/em&gt;. 200.&lt;/p&gt;

&lt;p&gt;Avneet Kaur, Jennifer Stratton, David Hutchins, and Nikki Mull. 2024. &lt;a href="https://doi.org/10.1145/3641233.3664338"&gt;Art-Directing Asha’s Braids in Disney’s Wish&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2024 Talks&lt;/em&gt;. Article 4.&lt;/p&gt;

&lt;p&gt;Avneet Kaur and Jennifer Stratton. 2024. &lt;a href="https://doi.org/10.1145/3641233.3664343"&gt;Character Stylization in Disney’s Wish&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2024 Talks&lt;/em&gt;. Article 5.&lt;/p&gt;

&lt;p&gt;John Kahrs, Patrick Osborne, Amol Sathe, Jeff Turley, Brian Whited, and Darrin Butters. 2012. &lt;a href="https://history.siggraph.org/learning/the-art-and-science-behind-walt-disney-animation-studios-paperman-by-kahrs-osborne-sathe-turley-and-whited/"&gt;The Art and Science Behind Walt Disney Animation Studios’ “Paperman”&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2012 Production Sessions&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Neelima Karanam, Joel Einhorn, Emily Vo, and Harmony M. Li. 2024. &lt;a href="https://doi.org/10.1145/3641233.3664337"&gt;Creating the Wishes of Rosas&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2024 Talks&lt;/em&gt;. Article 6.&lt;/p&gt;

&lt;p&gt;Michiyoshi Kuwahara, Kozaburo Hachimura, Shigeru Eiho, and Masato Kinoshita. 1976. &lt;a href="https://doi.org/10.1007/978-1-4684-0769-3_13"&gt;Processing of RI-Angiocardiographic Images&lt;/a&gt;. In &lt;em&gt;Digital Processing of Biomedical Images&lt;/em&gt;. 187-202.&lt;/p&gt;

&lt;p&gt;Harmony M. Li, George Rieckenberg, Neelima Karanam, Emily Vo, and Kelsey Hurley. 2024a. &lt;a href="https://doi.org/10.1145/3641233.3664335"&gt;Optimizing Assets for Authoring and Consumption in USD&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2024 Talks&lt;/em&gt;. Article 30.&lt;/p&gt;

&lt;p&gt;Harmony M. Li, Angela McBride, Sari Rodrig, and Gregory Culp. 2024b. &lt;a href="https://doi.org/10.1145/3641233.3664336"&gt;A Pipeline for Effective and Extensible Stylization&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2024 Talks&lt;/em&gt;. Article 51.&lt;/p&gt;

&lt;p&gt;Yining Karl Li, Charlotte Zhu, Gregory Nichols, Peter Kutz, Wei-Feng Wayne Huang, David Adler, Brent Burley, and Daniel Teece. 2024c. &lt;a href="https://doi.org/10.1145/3665320.3670993"&gt;Cache Points for Production-Scale Occlusion-Aware Many-Lights Sampling and Volumetric Scattering&lt;/a&gt;. In &lt;em&gt;Proc. of Digital Production Symposium (DigiPro 2024)&lt;/em&gt;. Article 6.&lt;/p&gt;

&lt;p&gt;Barbara J. Meier. 1996. &lt;a href="https://doi.org/10.1145/3665320.367099"&gt;Painterly Rendering for Animation&lt;/a&gt;. In &lt;em&gt;SIGGRAPH 1996: Proceedings of the 23rd Annual Conference on Computer Graphics and Interactive Techniques&lt;/em&gt;. 477-484.&lt;/p&gt;

&lt;p&gt;Mike Navarro and Jacob Rice. 2021. &lt;a href="https://doi.org/10.1145/3450623.3464652"&gt;Stylizing Volumes with Neural Networks&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2021 Talks&lt;/em&gt;. Article 54.&lt;/p&gt;

&lt;p&gt;Mike Navarro. 2023. &lt;a href="https://doi.org/10.1145/3587421.3595448"&gt;Diving Deeper Into Volume Style Transfer&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2023 Talks&lt;/em&gt;. Article 39.&lt;/p&gt;

&lt;p&gt;Jennifer Newfield and Josh Staub. 2020. &lt;a href="https://doi.org/10.1145/3388767.3409267"&gt;How Short Circuit Experiments: Experimental Filmmaking at Walt Disney Animation Studios&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2020 Talks&lt;/em&gt;. Article 72.&lt;/p&gt;

&lt;p&gt;Kyle Odermatt and Chris Springfield. 2002. &lt;a href="https://doi.org/10.1145/1242073.1242173"&gt;Creating 3D Painterly Environments for Disney’s “Treasure Planet”&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2002 Sketches &amp;amp; Applications&lt;/em&gt;. 160.&lt;/p&gt;

&lt;p&gt;Patrick Osborne and Josh Staub. 2014. &lt;a href="https://history.siggraph.org/learning/feast-a-look-at-walt-disney-animation-studios-newest-short-by-osborne-and-staub/"&gt;Feast – A Look at Walt Disney Animation Studios’ Newest Short&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2014 Production Sessions&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Johannes Schmid, Martin Sebastian Senn, Markus Gross, and Robert W. Sumner. 2011. &lt;a href="https://doi.org/10.1145/2010324.1964923"&gt;OverCoat: An Implicit Canvas for 3D Painting&lt;/a&gt;. &lt;em&gt;ACM Transactions on Graphics (Proc. of SIGGRAPH)&lt;/em&gt; 30, 4 (Jul. 2011), Article 28.&lt;/p&gt;

&lt;p&gt;Maryann Simmons and Brian Whited. 2014. &lt;a href="https://www.disneyanimation.com/publications/disneys-hair-pipeline-crafting-hair-styles-from-design-to-motion/"&gt;Disney’s Hair Pipeline: Crafting Hair Styles From Design to Motion&lt;/a&gt;. In &lt;em&gt;Eurographics 2014 Industrial Presentation&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Daniel Sýkora, Ladislav Kavan, Martin Čadik, Ondrej Jamriška, Alec Jacobson, Brian Whited, Maryann Simmons, and Olga Sorkine-Hornung. 2014. &lt;a href="https://doi.org/10.1145/2591011"&gt;Ink-and-Ray: Bas-relief Meshes for Adding Global Illumination Effects to Hand-Drawn Characters&lt;/a&gt;. &lt;em&gt;ACM Transactions on Graphics&lt;/em&gt; 33, 2 (Apr. 2016), Article 16.&lt;/p&gt;

&lt;p&gt;Rasmus Tamstorf, Ramón Montoya-Vozmediano, Daniel Teece, and Patrick Dalton. 2001. &lt;a href="https://history.siggraph.org/learning/hybrid-ink-line-rendering-in-a-production-environment-by-tamstorf-montoya-vozmediano-teece-and-dalton/"&gt;Hybrid Ink-Line Rendering in a Production Environment&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2001 Sketches &amp;amp; Applications&lt;/em&gt;. 201.&lt;/p&gt;

&lt;p&gt;Daniel Teece. 2003. &lt;a href="https://doi.org/10.1145/965400.965479"&gt;Sable - a Painterly Renderer for Film Animation&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2003 Sketches &amp;amp; Applications&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Marie Tollec and Mike Navarro. 2024. &lt;a href="https://doi.org/10.1145/3641233.3664340"&gt;Making Magic with 3D Volume Style Transfer&lt;/a&gt;. In  &lt;em&gt;ACM SIGGRAPH 2024 Talks&lt;/em&gt;. Article 48.&lt;/p&gt;

&lt;p&gt;Brian Whited, Eric Daniels, Michael Kaschalk, Patrick Osborne, and Kyle Odermatt. 2012. &lt;a href="https://doi.org/10.1145/2343045.2343071"&gt;Computer-Assisted Animation of Line and Paint in Disney’s Paperman&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2012 Talks&lt;/em&gt;. Article 19.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id="footnotes"&gt;Footnotes&lt;/h2&gt;

&lt;p&gt;&lt;sup id="2023-11-28-kuwahara-footnote-1"&gt;1&lt;/sup&gt; I recently learned that the Kuwahara filter originated from completely outside of graphics; it was originally invented at Kyoto University’s medical school and at Shiga University of Medical Science for medical imaging purposes. Specifically, it was invented for reducing noise in radioisotopic heart scans without blurring sharp features, and much later graphics people realized it made for a great edge-preserving blur for painting-like effects. I love when graphics intersects with other fields to produce interesting results!
&lt;a href="#2023-11-28-kuwahara-footnote-1-backlink"&gt;&lt;span class="material-symbols-outlined"&gt;keyboard_return&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</description><author>Code &amp;amp; Visuals</author><pubDate>Tue, 28 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.yiningkarlli.com/2023/11/wish.html</guid></item><item><title>Creativity: My Top 5 Feelings</title><link>https://martinrue.com/creativity-top-5/</link><description>Hearing that someone has enjoyed or found your work useful is an indicator that you're achieving the goal you set out to achieve, and that's an amazing feeling.</description><author>Martin Rue</author><pubDate>Tue, 28 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://martinrue.com/creativity-top-5/</guid></item><item><title>Good analogies</title><link>https://vit.baisa.cz/notes/projects/analogy/</link><description>a collection of analogies from various sources</description><author>Vít Baisa</author><pubDate>Tue, 28 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://vit.baisa.cz/notes/projects/analogy/</guid></item><item><title>Lex Fridman Podcast</title><link>https://vit.baisa.cz/notes/learn/lex-fridman-podcast/</link><description>notes on and scores of episodes</description><author>Vít Baisa</author><pubDate>Tue, 28 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://vit.baisa.cz/notes/learn/lex-fridman-podcast/</guid></item><item><title>graphic design is my passion</title><link>https://taylor.town/grapic-design</link><description>🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸</description><author>taylor.town</author><pubDate>Tue, 28 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/grapic-design</guid></item><item><title>Remembering Charlie Munger</title><link>https://www.danstroot.com/posts/2023-11-28-remembering-charlie-munger</link><description>&lt;img alt="post image" src="https://danstroot.imgix.net/assets/blog/img/charlie_munger.jpg" /&gt;&lt;br /&gt;&lt;br /&gt;Charlie Munger has been Warren Buffet’s partner since he joined Berkshire Hathaway as a vice chairman in 1978. In 1995 Charlie gave a speech at Harvard University about human decision-making and factors contributing to misjudgments. He wanted to share what he had observed and learned; models he used that helped him (and Berkshire Hathaway) succeed.&lt;br /&gt;&lt;br /&gt;This post &lt;a href="https://www.danstroot.com/posts/2023-11-28-remembering-charlie-munger"&gt;Remembering Charlie Munger&lt;/a&gt; first appeared on &lt;a href="https://www.danstroot.com"&gt;Dan Stroot's Blog&lt;/a&gt;</description><author>Dan Stroot</author><pubDate>Tue, 28 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.danstroot.com/posts/2023-11-28-remembering-charlie-munger</guid></item><item><title>Missing In Flutter SDK Docs, Your Axis Layout Guide</title><link>https://fredgrott.medium.com/missing-in-flutter-sdk-docs-your-axis-layout-guide-0b0b4e5ac8b4?source=rss-2cddef1829fb------2</link><description>&lt;div class="medium-feed-item"&gt;&lt;p class="medium-feed-image"&gt;&lt;a href="https://fredgrott.medium.com/missing-in-flutter-sdk-docs-your-axis-layout-guide-0b0b4e5ac8b4?source=rss-2cddef1829fb------2"&gt;&lt;img src="https://cdn-images-1.medium.com/max/1080/1*yp4negmlH7vgDW1MGA_Gqw.png" width="1080" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p class="medium-feed-snippet"&gt;One of the layout guides not in the Flutter SDK docs.&lt;/p&gt;&lt;p class="medium-feed-link"&gt;&lt;a href="https://fredgrott.medium.com/missing-in-flutter-sdk-docs-your-axis-layout-guide-0b0b4e5ac8b4?source=rss-2cddef1829fb------2"&gt;Continue reading on Medium »&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;</description><author>Stories by Fred Grott on Medium</author><pubDate>Mon, 27 Nov 2023 17:08:33 GMT</pubDate><guid isPermaLink="true">https://fredgrott.medium.com/missing-in-flutter-sdk-docs-your-axis-layout-guide-0b0b4e5ac8b4?source=rss-2cddef1829fb------2</guid></item><item><title>Suggested readings week 47/2023</title><link>https://www.stefanocosta.org/suggested-readings-week-47-2023/</link><description>&lt;hr /&gt;&lt;h3 id="ibm-researchs-northpole-chip-next-step-forward"&gt;&lt;a href="https://research.ibm.com/blog/northpole-ibm-ai-chip?ref=stefanocosta.org" rel="nofollow noreferrer noopener"&gt;IBM Research&amp;apos;s NorthPole chip: next step forward&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;IBM is one of those corporations that until now by slowly and steadly innovating has given humanity the building blocks of the greates technology advancements. A new chip prototype from IBM Research&amp;#x2019;s lab in California might be the next&lt;/p&gt;</description><author>Food for Thought (by Stefano Costa)</author><pubDate>Mon, 27 Nov 2023 15:30:42 GMT</pubDate><guid isPermaLink="true">https://www.stefanocosta.org/suggested-readings-week-47-2023/</guid></item><item><title>Foundations lead to structure</title><link>https://ilearnt.com/blog/foundationsleadtostructure/</link><description>&lt;p&gt;Foundations are important and need to be correct and strong before you build anything.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Mon, 27 Nov 2023 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/foundationsleadtostructure/</guid></item><item><title>Lessons from Career Coaching</title><link>https://rob.sh/post/coaching/</link><description>&lt;p&gt;In the autumn of 2022, I got an opportunity to attend an external leadership
course. One of the things that it involved was some coaching sessions. I didn’t
really think that talking to someone about work was something that I &lt;em&gt;needed&lt;/em&gt;
(and honestly probably didn’t understand what a coach did…), but engaged with
it anyway.&lt;/p&gt;
&lt;p&gt;Cue the start of 2023 — Google went through some big changes, and
unfortunately, my health took another downturn. At the same time, I was
personally struggling with balancing the work that I was doing leading a wider
technical team (especially with layoffs impacting their direct management),
driving an innovation project (which I was mostly doing out of hours), and
keeping myself healthy.&lt;/p&gt;</description><author>rob.sh</author><pubDate>Mon, 27 Nov 2023 13:27:01 GMT</pubDate><guid isPermaLink="true">https://rob.sh/post/coaching/</guid></item><item><title>`curl` your landing page</title><link>https://www.mootoday.com/blog/curl-your-landing-page</link><description>Does your project provide a CLI? Let people install it with `curl` from your marketing landing page.</description><author>www.mootoday.com - RSS Feed</author><pubDate>Mon, 27 Nov 2023 08:15:13 GMT</pubDate><guid isPermaLink="true">https://www.mootoday.com/blog/curl-your-landing-page</guid></item><item><title>Announcing the Pinecone AWS Reference Architecture in Pulumi</title><link>https://zackproser.com/blog/pinecone-reference-architecture-launch</link><description>Deploy production-ready systems using Pinecone in minutes with the AWS Reference Architecture</description><author>Zachary Proser</author><pubDate>Mon, 27 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/pinecone-reference-architecture-launch</guid></item><item><title>Pinecone AWS Reference Architecture Technical Walkthrough</title><link>https://zackproser.com/blog/pinecone-reference-architecture-technical-walkthrough</link><description>An examination of the Reference Architecture components and functionality</description><author>Zachary Proser</author><pubDate>Mon, 27 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/pinecone-reference-architecture-technical-walkthrough</guid></item><item><title>Deploying the Pinecone AWS Reference Architecture - Part 1</title><link>https://zackproser.com/videos/pinecone-ref-arch-deployment-1</link><description>I deploy the Pinecone AWS Reference Architecture end to end in this three part video series</description><author>Zachary Proser</author><pubDate>Mon, 27 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/videos/pinecone-ref-arch-deployment-1</guid></item><item><title>Deploying the Pinecone AWS Reference Architecture - Part 2</title><link>https://zackproser.com/videos/pinecone-ref-arch-deployment-2</link><description>I deploy the Pinecone AWS Reference Architecture end to end in this three part video series</description><author>Zachary Proser</author><pubDate>Mon, 27 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/videos/pinecone-ref-arch-deployment-2</guid></item><item><title>Deploying the Pinecone AWS Reference Architecture - Part 3</title><link>https://zackproser.com/videos/pinecone-ref-arch-deployment-3</link><description>I deploy the Pinecone AWS Reference Architecture end to end in this three part video series</description><author>Zachary Proser</author><pubDate>Mon, 27 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/videos/pinecone-ref-arch-deployment-3</guid></item><item><title>Destroying the Pinecone AWS Reference Architecture</title><link>https://zackproser.com/videos/pinecone-ref-arch-destruction</link><description>I demonstrate how to destroy the Pinecone AWS Reference Architecture using Pulumi</description><author>Zachary Proser</author><pubDate>Mon, 27 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/videos/pinecone-ref-arch-destruction</guid></item><item><title>Deploying a jump host for the Pinecone AWS Reference Architecture</title><link>https://zackproser.com/videos/pinecone-ref-arch-jumphost</link><description>I demonstrate how to deploy and connect through a jump host in order to interact with Postgres</description><author>Zachary Proser</author><pubDate>Mon, 27 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/videos/pinecone-ref-arch-jumphost</guid></item><item><title>Creating a Fast Time-Series Graph With Postgres Materialized Views</title><link>https://dylanpaulus.com/posts/2023/creating-fast-time-series-graph-with-postgres/</link><description>&lt;blockquote&gt;
&lt;p&gt;Imagine you have a massive amount of time-series data you want to explore and visualize. Seeing the latest trends, the historical patterns, and the outliers in your data can help you gain insights and make decisions. But how do you visualize and analyze time-series data effectively? How do you create graphs, plots, and other visualizations for real-time analytics showing the current state of your data and the historical changes over different time intervals? And how do you do it efficiently without sacrificing performance or accuracy?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;
&lt;p&gt;In this article with Timescale we dive into practical examples of using materialized views and continuous aggregates to create fast, performant time-series graphs.&lt;/p&gt;
&lt;/div&gt;</description><author>Dylan Paulus' Blog</author><pubDate>Mon, 27 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://dylanpaulus.com/posts/2023/creating-fast-time-series-graph-with-postgres/</guid></item><item><title>🏰 A Day Exploring Berlin</title><link>https://james-carr.org/posts/a-day-in-berlin/</link><description>Previously I wrote about setting out on my solo journey across Germany and how I prepared. Today I’m going to break down everything about my time in Berlin: places to visit and tons of photos to share. Along the way, I hope to share a few tips and tricks to make your visit enjoyable.
Arrival In Berlin After a 12 hour flight I landed in Berlin at 8:20am. Thanks to the sleeping gummies I took, I was wide awake and my legs weren’t sore or stiff thanks to the compression socks I wore.</description><author>James Carr</author><pubDate>Mon, 27 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://james-carr.org/posts/a-day-in-berlin/</guid></item><item><title>How a Release Failed</title><link>https://backendhance.com/blog/2023/how-a-release-failed/</link><description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;h3 id="nach-9-stunden-stand-die-entscheidung-fest-wir-müssen-zurückrollen"&gt;
&lt;a class="anchor" href="#nach-9-stunden-stand-die-entscheidung-fest-wir-m%c3%bcssen-zur%c3%bcckrollen"&gt;Nach 9 Stunden stand die Entscheidung fest: Wir müssen zurückrollen.&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Der erste Release seit fast 1,5 Jahren war gescheitert.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Und dabei lief es so gut. Die minutiöse Vorbereitung der letzten Wochen hatte sich ausgezahlt. Alles lief nach Plan. Die notwendigen Änderungen an den VMs verliefen wie erwartet. Die große Migration war nach einer Stunde Laufzeit erfolgreich durchgelaufen. Die neuen Contentfiles konnten erfolgreich mit dem neuen System deployed werden. Die neue CD Pipeline lief zum ersten Mal auf Production &amp;ndash; und war erfolgreich.&lt;/p&gt;</description><author>Backendhance</author><pubDate>Mon, 27 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/how-a-release-failed/</guid></item><item><title>Carta's 83(b) Oversight: A Tax Trap for Married Startup Employees</title><link>https://www.khanna.law/blog/carta-83b-oversight</link><description>There's a blind spot in Carta's 83(b) election feature you need to know about if you're married and living in California.</description><author>Khanna Law Blog</author><pubDate>Mon, 27 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.khanna.law/blog/carta-83b-oversight</guid></item><item><title>Why do companies hire people to be idle a lot of the time?</title><link>https://ntietz.com/blog/why-do-companies-hire-people-to-be-idle-a-lot-of-the-time/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;The biggest tech companies employ a lot of engineers.
In 2021, Microsoft employed over &lt;a href="https://devblogs.microsoft.com/engineering-at-microsoft/welcome-to-the-engineering-at-microsoft-blog/"&gt;100,000 software engineers&lt;/a&gt;.
That is just mind boggling scale to me.
It's roughly as many people as the whole &lt;em&gt;county&lt;/em&gt; I grew up in.&lt;/p&gt;
&lt;p&gt;They are paying a lot of engineers.
Some of them do very little, with employees saying they "were paid to do little-to-no work"&lt;sup class="footnote-reference" id="fr-1-1"&gt;&lt;a href="https://ntietz.com/blog/why-do-companies-hire-people-to-be-idle-a-lot-of-the-time/#fn-1"&gt;[1]&lt;/a&gt;&lt;/sup&gt;.
So... why are they paying them if there isn't a lot of work for them to do?&lt;/p&gt;
&lt;p&gt;There are a couple of theories for this.
The one I hear most often is that the big tech companies employ people to keep them off the market.
There might be some truth to that, but I think it's a small portion compared to another very reasonable explanation.&lt;/p&gt;
&lt;p&gt;Let's take a step back and use everyone's favorite software engineering reference: constructing physical infrastructure, like bridges!
I'm not comparing the two fields, but we can observe similar dynamics at play with construction crews.
We've all seen the construction crews where one person is operating machinery and four are standing around watching.
So the same question comes up here: why are they paying them if there isn't a lot of work for them to do?&lt;/p&gt;
&lt;p&gt;In the case of construction crews, I think it's quite obvious that they're not paying them to keep them off the market.
So there has to be another dynamic at play.
Most of the time, when we see people idle at a job site, they're actually either resting, waiting to be able to do their work, or supervising and inspecting.
But some of the time, you really &lt;em&gt;do&lt;/em&gt; have a lot of people at the site to do nothing.
It's that last part that I think is the key at play here in software, too.&lt;/p&gt;
&lt;p&gt;There are multiple metrics you can optimize for in construction projects.
Among these are total resource efficiency and total timeline speed.
There is a trade-off between these!
At the two extremes, you have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Maximize resource utilization and schedule crews based on when the work for them will be available. This increases timelines, but has better overall resource utilization by sending people only where they can be fully utilized, but that means the job site grinds to a halt when crews are not currently available.&lt;/li&gt;
&lt;li&gt;Minimize timeline by having everyone available for their portion immediately. This reduces total utilization of any individual worker, but also dramatically shortens the job because whoever is needed is always already available.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This sort of schedule compression is available for high-priority construction projects where it's essential to get it done as fast as possible.
It's also &lt;em&gt;very&lt;/em&gt; expensive, so it's not the typical mode.&lt;/p&gt;
&lt;p&gt;I think big companies do something similar in tech.
It's not a perfect comparison, because companies also &lt;em&gt;do&lt;/em&gt; have a lot of inefficiencies and the processes don't maximize for total throughput of features, but I see two modes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Startups (and other resource-constrained companies) often maximize utilization of employees, so some features and products take different timelines but everyone is at maximum utilization&lt;/li&gt;
&lt;li&gt;Big companies (without cash resource constraints) maximize for &lt;em&gt;other metrics&lt;/em&gt; so employees have more downtime. This slack allows the company to achieve other metrics by having people available at a moment's notice.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This isn't the full picture&lt;sup class="footnote-reference" id="fr-2-1"&gt;&lt;a href="https://ntietz.com/blog/why-do-companies-hire-people-to-be-idle-a-lot-of-the-time/#fn-2"&gt;[2]&lt;/a&gt;&lt;/sup&gt;—nothing ever is—but I think it gives a reasonable intuition for why idle engineers at big tech aren't just being kept off the market.
It's often the case that there's some other metric being optimized, not individual employee utilization.&lt;/p&gt;
&lt;p&gt;These companies have a lot of resources that they can throw at problems.
If there is a metric they want to optimize, it's a viable approach for them to hire a lot of people to be only somewhat utilized in order to achieve that other objective.
And that's why you see can this trend &lt;em&gt;reverse&lt;/em&gt; in leaner times.
The big tech companies have started to value full utilization more during this economy, since they are more cash constrained than previously.&lt;/p&gt;
&lt;hr /&gt;
&lt;hr /&gt;&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-1"&gt;
&lt;p&gt;From &lt;a href="https://www.businesstoday.in/technology/news/story/paid-employees-to-do-fake-work-laid-off-worker-accuses-meta-of-not-having-enough-work-for-staff-373423-2023-03-15"&gt;BusinessToday&lt;/a&gt;. &lt;a href="https://ntietz.com/blog/why-do-companies-hire-people-to-be-idle-a-lot-of-the-time/#fr-1-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-2"&gt;
&lt;p&gt;Other factors that also lead to idle engineers are prestige and budgets. It's considered prestigious to have a bigger organization reporting to you, so managers have an incentive to grow teams and departments even without work for them to do. And budgets must be used or else they'll be reallocated, so keeping and growing your headcount is a way to defend that budget for the future. &lt;a href="https://ntietz.com/blog/why-do-companies-hire-people-to-be-idle-a-lot-of-the-time/#fr-2-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 27 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/why-do-companies-hire-people-to-be-idle-a-lot-of-the-time/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>HNTV: play hn yt links locally</title><link>https://taylor.town/hntv</link><description>Play videos recently submitted to HN via the commandline.</description><author>taylor.town</author><pubDate>Mon, 27 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/hntv</guid></item><item><title>Upgrading to macOS 14 Sonoma broke my network connection</title><link>https://ellie.wtf/notes/sonoma-broken-internet/</link><description>&lt;p&gt;I upgraded my mac to macOS 14, Sonoma earlier today. Upon first using it, my internet wouldn't connect!&lt;/p&gt;
&lt;p&gt;Wifi or ethernet, nothing I tried work&lt;/p&gt;
&lt;p&gt;Even&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;ping 8.8.8.8&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I did some searching, and it turns out the version of &lt;a href="https://www.obdev.at/products/littlesnitch/index.html" rel="external"&gt;Little Snitch&lt;/a&gt; I had installed wasn't supported.&lt;/p&gt;
&lt;p&gt;The issue here is that without internet, I couldn't update it&lt;/p&gt;
&lt;p&gt;The fix wasn't too difficult. Go to System Settings -&amp;gt; Network -&amp;gt; VPNs and Filters. Disable little snitch.&lt;/p&gt;</description><author>Ellie's Notes</author><pubDate>Mon, 27 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ellie.wtf/notes/sonoma-broken-internet/</guid></item><item><title>Default Apps (2023)</title><link>https://brntn.me/blog/default-apps-2023/</link><description>&lt;p&gt;It feels a little no effort November, but I've seen this list pop up &lt;a href="https://chriscoyier.net/2023/11/25/default-apps-2023/"&gt;on&lt;/a&gt; &lt;a href="https://sylvia.micro.blog/2023/11/05/my-defaults.html"&gt;a&lt;/a&gt; &lt;a href="https://www.benji.dog/articles/defaults-2023/"&gt;few&lt;/a&gt; &lt;a href="https://birchtree.me/blog/my-default-apps-at-the-end-of-2023/"&gt;blogs&lt;/a&gt; and figured I would add mine so I can compare next year. You can see a great list of these from the &lt;a href="https://defaults.rknight.me/"&gt;Hemispheric Views&lt;/a&gt; podcast listeners.&lt;/p&gt;
&lt;p&gt;Just my personal list (work is sometimes quite different)&lt;/p&gt;
&lt;p&gt;📨 Mail Client: Fastmail web and iOS app&lt;br /&gt;
📮 Mail Server: Fastmail&lt;br /&gt;
📝 Notes: Apple Notes, &lt;a href="https://sesh.github.io/scratchpad/"&gt;Scratchpad&lt;/a&gt;&lt;br /&gt;
✅ To-Do: Reminders&lt;br /&gt;
📷 Photo Shooting: Camera (iOS)&lt;br /&gt;
🟦 Photo Management: Photos (Mac and iOS)&lt;br /&gt;
📆 Calendar: Fastmail via Fantastical&lt;br /&gt;
📁 Cloud File Storage: iCloud Drive&lt;br /&gt;
📖 RSS: Feedbin via Feedbin web and NetNewsWire&lt;br /&gt;
🌐 Browser: Firefox, Safari (iOS)&lt;br /&gt;
🔖 Bookmarks: &lt;a href="https://github.com/sesh/bm2"&gt;bm2&lt;/a&gt;&lt;br /&gt;
📜 Word Processing: Google Docs&lt;br /&gt;
📈 Spreadsheets: Google Sheets&lt;br /&gt;
🛒 Shopping Lists: Apple Notes&lt;br /&gt;
🎵 Music: Apple Music&lt;br /&gt;
🎤 Podcasts: Overcast&lt;br /&gt;
🔐 Password Management: 1password&lt;/p&gt;
&lt;p&gt;Currently looking for: a Read it Later approach that works for my scattered workflow.&lt;/p&gt;
&lt;p&gt;In the past I've kept ordered lists of apps I use (&lt;a href="https://brntn.me/w/apps/2023/"&gt;2023&lt;/a&gt; vs &lt;a href="https://brntn.me/w/apps/2019/"&gt;2019&lt;/a&gt;) and it turns out I don't change things up that often.&lt;/p&gt;</description><author>Brenton Cleeland</author><pubDate>Mon, 27 Nov 2023 01:58:24 GMT</pubDate><guid isPermaLink="true">https://brntn.me/blog/default-apps-2023/</guid></item><item><title>Resumé</title><link>https://seirdy.one/about/resume/</link><description>Detail-oriented, committed, self-motivated, OSS enthusiast proficient in Python, Go, Linux/UNIX systems, and cloud-native computing looking for an internship.</description><author>All content on Seirdy’s Home</author><pubDate>Mon, 27 Nov 2023 00:36:32 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/about/resume/</guid></item><item><title>Skateboard 2</title><link>https://mfashby.net/posts/2023-11-26-skateboard-2/</link><description/><author>Home on mfashby.net</author><pubDate>Sun, 26 Nov 2023 22:33:27 GMT</pubDate><guid isPermaLink="true">https://mfashby.net/posts/2023-11-26-skateboard-2/</guid></item><item><title>AI-Powered RPGs: The Next Gaming Revolution Inspired by Cicero</title><link>https://mitrapunk.com/ai-powered-rpgs-the-next-gaming-revolution-inspired-by-cicero/</link><description>AI-powered RPGs, inspired by Cicero's AI breakthrough, are revolutionizing gaming with dynamic worlds and responsive NPCs.</description><author>Mitrapunk: Engineering Game</author><pubDate>Sun, 26 Nov 2023 22:13:12 GMT</pubDate><guid isPermaLink="true">https://mitrapunk.com/ai-powered-rpgs-the-next-gaming-revolution-inspired-by-cicero/</guid></item><item><title>ShareX image effects</title><link>https://river.me/blog/sharex-image-effects/</link><description>How to use ShareX image effects &amp;amp; spam your friends with rainbows</description><author>River Writes - A MediaWiki Blog</author><pubDate>Sun, 26 Nov 2023 18:40:47 GMT</pubDate><guid isPermaLink="true">https://river.me/blog/sharex-image-effects/</guid></item><item><title>Remembering Frank H. Lutz</title><link>https://bastian.rieck.me/blog/2023/frank_lutz/</link><description>&lt;p&gt;I was deeply saddened to learn about the untimely death of
&lt;a href="https://www3.math.tu-berlin.de/IfM/Nachrufe/Frank_Lutz/"&gt;Frank H. Lutz&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I was not closely acquainted with Frank as a person but his work had a big impact on
me. In my undergraduate days, I discovered his resource &lt;a href="https://page.math.tu-berlin.de/~lutz/stellar/"&gt;The Manifold
Page&lt;/a&gt; at a critical
time in my studies when I was actively looking for ways to combine my
love for algorithms with my love for geometry and topology. At the
suggestion of &lt;a href="https://www.mathi.uni-heidelberg.de/~banagl/"&gt;Markus Banagl&lt;/a&gt;,
I looked up Frank&amp;rsquo;s website and was immediately hooked: here, then, was
someone who provided a glimpse into manifolds and how to &amp;lsquo;operate&amp;rsquo; with
them algorithmically! I used Frank&amp;rsquo;s triangulations of 2-manifolds and
3-manifolds to deepen my understanding of simplicial homology, writing
&lt;a href="https://github.com/Pseudomanifold/CompHom"&gt;a simple program to compute homology groups&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Subsequently, I kept returning to Frank&amp;rsquo;s resources, both for what one
might call &amp;rsquo;experimental mathematics,&amp;rsquo; but, truth be told, also just for
fun. I started looking into some &lt;a href="https://bastian.rieck.me/blog/2017/homology_statistics_2_manifolds/"&gt;homology statistics&lt;/a&gt;, for example.
Even later, as my research had expanded more into the realm of
computational topology, Frank graciously provided me an opportunity to
participate in a &lt;a href="https://www3.math.tu-berlin.de/mathplus/TES-Summer2021/"&gt;Thematic Mini-Conference on Computational Topology and
Machine
Learning&lt;/a&gt;.
Being on the job market at this point, I was honoured and excited about
presenting my work&amp;mdash;and I will be forever grateful for
it! For Frank, this was apparently not a big deal, but for me it
provided an unprecedented opportunity, and I felt that I had now finally
arrived in a nice community.&lt;/p&gt;
&lt;p&gt;I had just planned another project that would involve more of Frank&amp;rsquo;s
data and wanted to reach out to him when I learned the sorrowful news of his
passing. I am saddened that this opportunity will never come.&lt;/p&gt;
&lt;p&gt;I know that Frank&amp;rsquo;s triangulations are only a small part of his legacy,
but I want his friends and family to know that it is still an important
part for some people.&lt;/p&gt;
&lt;p&gt;May Frank&amp;rsquo;s memory be a blessing to his loved ones.&lt;/p&gt;</description><author>Ecce Homology on Bastian Grossenbacher Rieck's personal homepage</author><pubDate>Sun, 26 Nov 2023 18:00:00 GMT</pubDate><guid isPermaLink="true">https://bastian.rieck.me/blog/2023/frank_lutz/</guid></item><item><title>How To Write Flutter Adaptive Scaffold Breakpoints Like Were A GDE</title><link>https://fredgrott.medium.com/how-to-write-flutter-adaptive-scaffold-breakpoints-like-were-a-gde-44a152a22dc8?source=rss-2cddef1829fb------2</link><description>&lt;div class="medium-feed-item"&gt;&lt;p class="medium-feed-image"&gt;&lt;a href="https://fredgrott.medium.com/how-to-write-flutter-adaptive-scaffold-breakpoints-like-were-a-gde-44a152a22dc8?source=rss-2cddef1829fb------2"&gt;&lt;img src="https://cdn-images-1.medium.com/max/1080/1*FQQEl8eKIJ0YaDojAzh9zA.png" width="1080" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p class="medium-feed-snippet"&gt;Flutter Adaptive Scaffold UX pattern is not in the Flutter SDK, this is the first step in that UX pattern in the form of breakpoints like&amp;#x2026;&lt;/p&gt;&lt;p class="medium-feed-link"&gt;&lt;a href="https://fredgrott.medium.com/how-to-write-flutter-adaptive-scaffold-breakpoints-like-were-a-gde-44a152a22dc8?source=rss-2cddef1829fb------2"&gt;Continue reading on Medium »&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;</description><author>Stories by Fred Grott on Medium</author><pubDate>Sun, 26 Nov 2023 17:11:37 GMT</pubDate><guid isPermaLink="true">https://fredgrott.medium.com/how-to-write-flutter-adaptive-scaffold-breakpoints-like-were-a-gde-44a152a22dc8?source=rss-2cddef1829fb------2</guid></item><item><title>How to determine app idea is technically feasible as a non-technical founder?</title><link>https://vadimkravcenko.com/qa/determine-idea-technically-feasible/</link><description>&lt;p&gt;Question:&lt;/p&gt;
&lt;p&gt;I'm a non-technical founder with a concept for a productivity app that sets screen time limits and rewards users for staying below these limits. However, I'm struggling to determine if this idea is technically feasible, especially considering the data access restrictions imposed by platforms like iOS. Despite extensive research on Apple's developer forums I'm still not sure. I'm seeking advice on how a non-technical founder like myself should approach verifying the technical feasibility of such an app idea before committing to bringing on technical co-founders or starting development.&lt;/p&gt;
&lt;p&gt; Answer:&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://vadimkravcenko.com/qa/determine-idea-technically-feasible/"&gt;How to determine app idea is technically feasible as a non-technical founder?&lt;/a&gt; appeared first on &lt;a href="https://vadimkravcenko.com"&gt;Vadim Kravcenko&lt;/a&gt;.&lt;/p&gt;</description><author>Vadim Kravcenko</author><pubDate>Sun, 26 Nov 2023 14:46:50 GMT</pubDate><guid isPermaLink="true">https://vadimkravcenko.com/qa/determine-idea-technically-feasible/</guid></item><item><title>Abstract art</title><link>https://ilearnt.com/blog/abstractart/</link><description>&lt;p&gt;I have never understood abstract art. I have tried.&lt;/p&gt;
&lt;p&gt;I am not a big art lover but when I have viewed art I tend to be at the photo-realistic end as, to me, that shows the skill of the artist.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Sun, 26 Nov 2023 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/abstractart/</guid></item><item><title>Robot Dad</title><link>http://blog.untrod.com/2023/11/robot-dad.html</link><description>&lt;p&gt;&lt;em&gt;See discussion of this post on &lt;a href="https://news.ycombinator.com/item?id=38433330"&gt;Hacker News&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Tired of Alexa's feeble "here's what I found on wikianswerspam.com" responses to my eight-year-old son's science questions, I whipped up Robot Dad during my Thanksgiving break. He now runs in the background of our family computer.&lt;/p&gt;
&lt;p&gt;Robot Dad sounds like real …&lt;/p&gt;</description><author>Untrod</author><pubDate>Sun, 26 Nov 2023 10:00:00 GMT</pubDate><guid isPermaLink="true">http://blog.untrod.com/2023/11/robot-dad.html</guid></item><item><title>A weekly task list with the Alastair Method</title><link>http://evantravers.com/articles/2023/11/26/a-weekly-task-list-with-the-alastair-method/</link><description>&lt;p&gt;I had never encountered the Alastair system, but I think this could work very well for me and be the secret to a weekly/monthly collection actually working for me.&lt;/p&gt;
&lt;p&gt;Rach writes:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Alastair Method for bullet journaling is when you place some vertical columns next to your list of tasks. The columns could be anything you want to group/organise your tasks by. I choose days. So, I have Monday to Sunday as my columns next to the list. And then, if I plan to do the task on a specific day, I will add a dot to the column. If the task is done, I cross it off in the day I did it and the overall list.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="her image of the system" src="https://rachsmith.comhttps://images.evantravers.com/alastair-weekly.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Do I go all in on analog for my tasks next year… tempting…&lt;/p&gt;&lt;p&gt;&lt;a class="u-in-reply-to  is-reply" href="https://rachsmith.com/weekly-tasks/"&gt;A weekly task list with the Alastair Method - rachsmith.com&lt;/a&gt;&lt;/p&gt;</description><author>trv.rs</author><pubDate>Sun, 26 Nov 2023 08:11:00 GMT</pubDate><guid isPermaLink="true">http://evantravers.com/articles/2023/11/26/a-weekly-task-list-with-the-alastair-method/</guid></item><item><title>I Made Heston Blumenthal's Chicken</title><link>https://matteoraso.github.io/i-made-heston-blumenthals-chicken.html</link><description>&lt;p&gt;Have you ever seen a recipe that looked so wild and out there, you can't help but feel like you need to try it at least once in your life? That's how I felt when I saw Heston's recipe for crispy roast chicken. You start off by brining the chicken …&lt;/p&gt;</description><author>Matteo's Website</author><pubDate>Sun, 26 Nov 2023 07:00:00 GMT</pubDate><guid isPermaLink="true">https://matteoraso.github.io/i-made-heston-blumenthals-chicken.html</guid></item><item><title>Some site optimizations</title><link>https://seirdy.one/notes/2023/11/25/some-site-optimizations/</link><description>&lt;p&gt;I continue to work on my site over break. Today I got the build time down to 3.5 seconds for the Hugo build and just under 3 seconds for post-processing and syntax-checking (mostly using &lt;code&gt;xmllint&lt;/code&gt;). I’m not parallelizing the post-processing since my laptop just has two cores (no simultaneous multithreading) and CI has one.&lt;/p&gt;&lt;p&gt;Seven seconds is still too slow for changes to feel instant, though. I can’t just rebuild on save. It’s especially slow for a site with only &amp;lt;200 pages.&lt;/p&gt;&lt;p&gt;Hugo’s profiler tells me that a lot of the time is spent searching for Webmentions that match the current page. Perhaps I should do that server-side ahead of time; right now, I download all my Webmentions as one big fat JSON file and load it into memory once. I should also move some post-processing back into Hugo.&lt;/p&gt;&lt;p&gt;I also re-used &lt;code&gt;xmllint&lt;/code&gt; to minify my feeds and check their syntax. The minification worked particularly well since my feeds contain raw, un-escaped XHTML. Some clients still fetch my full-text feeds without transparent compression.&lt;/p&gt;</description><author>All content on Seirdy’s Home</author><pubDate>Sun, 26 Nov 2023 05:18:24 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/notes/2023/11/25/some-site-optimizations/</guid></item><item><title>Gridnotes - an infinite 2D text editor</title><link>https://sidsite.com/posts/gridnotes/</link><description>Gridnotes is an infinite 2D text editor I made.</description><author>sidsite</author><pubDate>Sun, 26 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://sidsite.com/posts/gridnotes/</guid></item><item><title>Measure twice, vise thrice.</title><link>https://taylor.town/vises</link><description>Vises reduce movement in undesirable directions. They help you focus your energy in limited axes.</description><author>taylor.town</author><pubDate>Sun, 26 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/vises</guid></item><item><title>My Book Reviews</title><link>https://taylor.town/books</link><description>&lt;p&gt;&lt;small class="footnote" id="footnote-note"&gt;Note: There are books listed here whose views I do not endorse. I try
    to break beyond my bubble and understand others. If you're interested in my
    current beliefs, feel free to &lt;a href="mailto:hello@taylor.town"&gt;email me&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th id="" style="text-align: right;"&gt;&lt;/th&gt;
&lt;th id=""&gt;&lt;/th&gt;
&lt;th id="" style="text-align: left;"&gt;&lt;/th&gt;
&lt;th id="" style="text-align: left;"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-06-22&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Thinking in Bets&lt;/em&gt; :: Annie Duke&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-06-18&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Crowfolk&lt;/em&gt; :: Mark Stay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-06-08&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Think Faster, Talk Smarter&lt;/em&gt; :: Matt Abrahams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-05-29&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Someone Who Will Love You in All Your Damaged Glory&lt;/em&gt; :: Raphael Bob-Waksberg&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-05-18&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Trick Mirror&lt;/em&gt; :: Jia Tolentino&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-05-17&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;A Wizard of Earthsea&lt;/em&gt; :: Ursula K. Le Guin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-05-16&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Heart of Darkness&lt;/em&gt; :: Joseph Conrad&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-05-15&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Think Big, Act Small&lt;/em&gt; :: Jason Jennings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-05-14&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;First Love, Last Rites&lt;/em&gt; :: Ian McEwan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-05-13&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;I Deliver Parcels in Beijing&lt;/em&gt; :: Hu Anyan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-05-03&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Secret Life of Groceries&lt;/em&gt; :: Benjamin Lorr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-04-29&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The New Me&lt;/em&gt; :: Halle Butler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;♡&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-04-27&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Useful Not True&lt;/em&gt; :: Derek Sivers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-04-25&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Childhood's End&lt;/em&gt; :: Arthur C. Clarke&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-04-21&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Daydreamer&lt;/em&gt; :: Ian McEwan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-04-21&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Normal People&lt;/em&gt; :: Sally Rooney&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-04-18&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Prey&lt;/em&gt; :: Michael Chrichton&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-04-18&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Kindred&lt;/em&gt; :: Octavia Butler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-03-23&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Deficit Myth&lt;/em&gt; :: Stephanie Kelton&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-03-23&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Understanding Power&lt;/em&gt; :: Noam Chomsky&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-03-23&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;How to Hide an Empire&lt;/em&gt; :: Daniel Immerwahr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-03-16&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Oranges&lt;/em&gt; :: John MacPhee&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-03-15&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Wittgenstein's Mistress&lt;/em&gt; :: David Markson&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-03-14&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Middle C&lt;/em&gt; :: William H. Gass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-03-12&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Careless People&lt;/em&gt; :: Sarah Wynn-Williams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-03-10&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Decadent Society&lt;/em&gt; :: Ross Douthat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-03-08&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Dawn&lt;/em&gt; :: Octavia Butler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-02-20&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Uncommon Carriers&lt;/em&gt; :: John MacPhee&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-02-20&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Beginning of Infinity&lt;/em&gt; :: David Deutsch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-02-18&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Sphere&lt;/em&gt; by Michael Chrichton&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;♡&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-01-19&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Infinite Library&lt;/em&gt; :: Alexander Wales&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;♡&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2026-01-12&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Building Strongholds&lt;/em&gt; :: Alexander Wales&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-12-27&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Hard Thing About Hard Things&lt;/em&gt; :: Ben Horowitz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-12-27&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Man Who Broke Capitalism&lt;/em&gt; :: David Gelles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;♡&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-12-24&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Trust and Consequences&lt;/em&gt; :: Alexander Wales&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-12-20&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Eragon&lt;/em&gt; :: Christopher Paolini&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;♡&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-12-11&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Through Adversity&lt;/em&gt; :: Alexander Wales&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-12-06&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;House of Suns&lt;/em&gt; :: Alastair Reynolds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-11-10&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Version Control&lt;/em&gt; :: Dexter Palmer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-11-10&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Meditations for Mortals&lt;/em&gt; :: Oliver Burkeman&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-10-13&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Matrix&lt;/em&gt; :: Lauren Groff&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-10-05&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Wizard and the Prophet&lt;/em&gt; :: Charles C. Mann&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-10-04&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Blindsight&lt;/em&gt; :: Peter Watts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-10-03&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Vellum&lt;/em&gt; :: Hal Duncan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-09-24&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Breakneck&lt;/em&gt; :: Dan Wang&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-09-21&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Klara and the Sun&lt;/em&gt; :: Kazuo Ishiguro&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-09-20&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Curse of the Mogul&lt;/em&gt; :: Jonathan Knee, Bruce Greenwald, and Ava Seave&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-09-19&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Gideon the Ninth&lt;/em&gt; :: Tamsyn Muir&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-09-17&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Why Greatness Cannot Be Planned&lt;/em&gt; :: Kenneth O. Stanley and Joel Lehman&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-09-14&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Characteristics of Games&lt;/em&gt; :: George Skaff Elias, Richard Garfield, and K. Robert Gutschera&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-09-07&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Children of Time&lt;/em&gt; :: Adrian Tchaikovsky&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-09-02&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Good to Great&lt;/em&gt; :: Jim C. Collins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-09-02&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Devil Take the Hindmost&lt;/em&gt; :: Edward Chancellor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-09-02&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Grid&lt;/em&gt; :: Gretchen Bakke&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-08-30&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Wisdom of Insecurity&lt;/em&gt; :: Alan Watts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-08-30&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;What Makes Sammy Run?&lt;/em&gt; :: Buddy Schulberg&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-08-10&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Wager&lt;/em&gt; :: David Grann&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-08-05&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Black Rednecks and White Liberals&lt;/em&gt; :: Thomas Sowell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-08-01&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Waybound&lt;/em&gt; :: Will Wight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-07-29&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Dreadgod&lt;/em&gt; :: Will Wight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-07-26&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Reaper&lt;/em&gt; :: Will Wight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-07-23&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Bloodline&lt;/em&gt; :: Will Wight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-07-23&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Can't Hurt Me&lt;/em&gt; :: David Goggins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-07-23&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Become What You Are&lt;/em&gt; :: Alan Watts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-07-19&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Wintersteel&lt;/em&gt; :: Will Wight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-07-11&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Uncrowned&lt;/em&gt; :: Will Wight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-07-09&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;How to Get Filthy Rich in Rising Asia&lt;/em&gt; :: Mohsin Hamid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-07-08&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Underlord&lt;/em&gt; :: Will Wight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-07-07&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Wealth of Humans&lt;/em&gt; :: Ryan Avent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-07-06&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Ghostwater&lt;/em&gt; :: Will Wight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-07-06&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Skysworn&lt;/em&gt; :: Will Wight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-07-05&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Blackflame&lt;/em&gt; :: Will Wight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-07-01&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Soulsmith&lt;/em&gt; :: Will Wight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-07-01&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Everything is Tuberculosis&lt;/em&gt; :: John Green&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-06-25&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;An Absolutely Remarkable Thing&lt;/em&gt; :: Hank Green&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-06-23&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Unsouled&lt;/em&gt; :: Will Wight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-06-23&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Steve Jobs&lt;/em&gt; :: Walter Isaacson&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#zen-happiness"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-05-10&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Zen and the Art of Happiness&lt;/em&gt; :: Chris Prentiss&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#prime-conspiracy"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-05-10&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The The Prime Number Conspiracy&lt;/em&gt; :: Thomas Lin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#soulhome"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-05-10&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Soulhome&lt;/em&gt; :: Sarah Lin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#good-inside"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-04-30&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Good Inside&lt;/em&gt; :: Dr. Becky Kennedy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#factfulness"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-04-29&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Factfulness&lt;/em&gt; :: Hans Rosling, Ola Rosling, Anna Rosling Rönnlund&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#give-and-take"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-04-27&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Give and Take&lt;/em&gt; :: Adam Grant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#bullshit"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-04-21&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;On Bullshit&lt;/em&gt; :: Harry G. Frankfurt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#elegant-puzzle"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-04-17&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;An Elegant Puzzle&lt;/em&gt; :: Will Larson&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#how-innovation-works"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-04-16&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;How Innovation Works&lt;/em&gt; :: Matt Ridley&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#musashi"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-04-16&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Musashi&lt;/em&gt; :: Eiji Yoshikawa&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#mom-test"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-03-20&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Mom Test&lt;/em&gt; :: Rob Fitzpatrick&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#motivational-interviewing"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-03-09&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Motivational Interviewing&lt;/em&gt; :: William R. Miller and Stephen Rollnick&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#golden-compass"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-03-06&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Golden Compass&lt;/em&gt; :: Philip Pullman&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#selfish-reasons"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-03-04&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Selfish Reasons to Have More Kids&lt;/em&gt; :: Bryan Caplan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#three-laws-of-nature"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-03-03&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Three Laws of Nature&lt;/em&gt; :: R. Stephen Berry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#sirens-muses"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-02-28&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Sirens &amp; Muses&lt;/em&gt; :: Antonia Angress&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#creation-lake"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-02-14&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Creation Lake&lt;/em&gt; :: Rachel Kushner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#ministry-of-time"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-02-07&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Ministry of Time&lt;/em&gt; :: Kaliane Bradley&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#complexity"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-02-14&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Complexity: A Guided Tour&lt;/em&gt; :: Melanie Mitchell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#supercommunicators"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-02-13&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Supercommunicators&lt;/em&gt; :: Charles Duhill&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#evil-politicians"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-02-10&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;How Evil Are Politicians&lt;/em&gt; :: Bryan Caplan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#spiders-war"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-02-10&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Spider's War&lt;/em&gt; :: Daniel Abraham&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#case-against-education"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-02-09&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Case Against Education&lt;/em&gt; :: Bryan Caplan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#radical-abundance"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-02-09&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Radical Abundance&lt;/em&gt; :: K. Eric Drexler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#how-to-talk"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-02-09&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;How to Talk So Kids Will Listen &amp; Listen So Kids Will Talk&lt;/em&gt; :: Adele Faber &amp;amp; Elaine Mazlish&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#widows-house"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-02-08&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Widow's House&lt;/em&gt; :: Daniel Abraham&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#naked-singularity"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-02-06&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;A Naked Singularity&lt;/em&gt; :: Sergio de la Pava&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#theory-of-oblivion"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-02-01&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;A General Theory of Oblivion&lt;/em&gt; :: José Eduardo Agualusa&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#impossible-man"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-01-25&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Impossible Man&lt;/em&gt; :: Patchen Barss&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#tyrants-law"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-01-20&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Tyrant's Law&lt;/em&gt; :: Daniel Abraham&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#road"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-01-06&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Road&lt;/em&gt; :: Cormac McCarthy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#prisoners-of-geography"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2025-01-01&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Prisoners of Geography&lt;/em&gt; :: Tim Marshall&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#kings-blood"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-12-31&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The King's Blood&lt;/em&gt; :: Daniel Abraham&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#cadillac-desert"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-12-17&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Cadillac Desert&lt;/em&gt; :: Marc Reisner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#welcome-bookshop"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-12-16&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Welcome to the Hyanam-dong Bookshop&lt;/em&gt; :: Hwang Bo-Reum&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#thinking-with-type"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-12-16&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Thinking with Type&lt;/em&gt; :: Ellen Lupton&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#quitting-social-media"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-12-15&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;James Acaster's Guide to Quitting Social Media&lt;/em&gt; :: James Acaster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#fab"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-12-14&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Fab&lt;/em&gt; :: Neil Gershenfeld&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="#plow"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-11-28&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Drive Your Plow Over the Bones of the Dead&lt;/em&gt; :: Olga Tokarczuk Antonia Lloyd&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#tyll"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-11-27&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Tyll&lt;/em&gt; :: Daniel Kehlmann&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#glucose-revolution"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-11-22&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Glucose Revolution&lt;/em&gt; :: Jessie Inchauspe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#idiot"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-11-20&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Idiot&lt;/em&gt; :: Elif Batuman&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#dragons-path"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-11-19&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Dragon's Path&lt;/em&gt; :: Daniel Abraham&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#oblivion-stories"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-11-13&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Oblivion: Stories&lt;/em&gt; :: David Foster Wallace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#dream-machine"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-11-13&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Dream Machine&lt;/em&gt; :: M. Mitchell Waldrop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#heaven"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-11-04&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Heaven&lt;/em&gt; :: Mieko Kawakami&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#the-employees"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-11-04&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Employees&lt;/em&gt; :: Olga Ravn&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#convenience-store-woman"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-11-03&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Convenience Store Woman&lt;/em&gt; :: Sayaka Murata&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#nexus"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-11-01&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Nexus&lt;/em&gt; :: Yuval Noah Harari&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#capitalist-realism"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-10-31&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Capitalist Realism&lt;/em&gt; :: Mark Fisher&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#no-country"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-10-24&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;No Country for Old Men&lt;/em&gt; :: Cormac McCarthy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#strangest"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-10-21&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Strangest Man&lt;/em&gt; :: Graham Farmelo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#trust"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-10-18&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Trust&lt;/em&gt; :: Hernan Diaz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#pastoralia"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-10-17&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Pastoralia&lt;/em&gt; :: George Saunders&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#atonement"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-10-16&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Atonement&lt;/em&gt; :: Ian McEwan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#when-we-cease"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-10-15&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;When We Cease to Understand the World&lt;/em&gt; :: Benjamín Labatut&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#the-friend"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-10-11&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Friend&lt;/em&gt; :: Signid Nunez&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="#hateship"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-10-10&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Hateship, Friendship, Courtship, Loveship, Marriage&lt;/em&gt; :: Alice Munroe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#new-machine"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-10-09&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Soul of a New Machine&lt;/em&gt; :: Tracy Kidder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#seeing-state"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-10-06&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Seeing Like a State&lt;/em&gt; :: James C. Scott&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#stay-true"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-09-25&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Stay True&lt;/em&gt; :: Hua Hsu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#evicted"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-09-16&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Evicted&lt;/em&gt; :: Matthew Desmond&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#olive"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-09-10&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Olive Kitteridge&lt;/em&gt; :: Elizabeth Strout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#shining"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-09-04&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;A Shining&lt;/em&gt; :: Jon Fosse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#outline"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-09-01&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Outline&lt;/em&gt; :: Rachel Cusk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#glass-cage"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-08-30&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Glass Cage&lt;/em&gt; :: Nicholas Carr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#volt-rush"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-08-29&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Volt Rush&lt;/em&gt; :: Henry Sanderson&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#demon-copperhead"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-08-28&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Demon Copperhead&lt;/em&gt; :: Barbara Kingsolver&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#world-sale"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-08-13&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The World for Sale&lt;/em&gt; :: Javier Blas &amp;amp; Jack Farchy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#roadside-picnic"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-08-11&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Roadside Picnic&lt;/em&gt; :: Arkady &amp;amp; Boris Strugatsky&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="#war-below"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-08-06&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The War Below&lt;/em&gt; :: Ernest Scheyder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#frostbite"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-08-06&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Frostbite&lt;/em&gt; :: Nicola Twilley&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#newts"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-08-06&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;War with the Newts&lt;/em&gt; :: Karel Capek&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#leviathan"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-08-03&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Leviathan&lt;/em&gt; :: Paul Auster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#enchanted-objects"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-08-03&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Enchanted Objects&lt;/em&gt; :: David Rose&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#long-earth"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-07-30&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Long Earth&lt;/em&gt; :: Stephen Baxter &amp;amp; Terry Pratchett&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#alignment"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-07-29&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Alignment Problem&lt;/em&gt; :: Brian Christian&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#impossible-conversations"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-07-26&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;How to Have Impossible Conversations&lt;/em&gt; :: Peter Boghossian &amp;amp; James Lindsay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#sellout"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-07-23&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Sellout&lt;/em&gt; :: Paul Beatty&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#slow-productivity"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-07-22&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Slow Productivity&lt;/em&gt; :: Cal Newport&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;♡&lt;/td&gt;
&lt;td&gt;&lt;a href="#quantations"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-07-19&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Quantations&lt;/em&gt; :: Joseph A. Stirt, M.D.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#sparrow"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-07-19&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Sparrow&lt;/em&gt; :: Mary Doria Russell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#co-intelligence"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-07-18&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Co-Intelligence&lt;/em&gt; :: Ethan Mollick&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#politics-of-design"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-07-14&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Politics of Design&lt;/em&gt; :: Ruben Pater&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#the-overstory"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-07-13&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Overstory&lt;/em&gt; :: Richard Powers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#user-friendly"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-06-27&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;User-Friendly&lt;/em&gt; :: Cliff Kuang &amp;amp; Robert Fabricant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#cold-start"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-06-19&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Cold Start Problem&lt;/em&gt; :: Andrew Chen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#going-infinite"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-06-15&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Going Infinite&lt;/em&gt; :: Michael Lewis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#build"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-06-15&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Build&lt;/em&gt; :: Tony Fadell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#rise-of-td"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-06-14&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Rise of Theodore Roosevelt&lt;/em&gt; :: Edmund Morris&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#grabcad"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-06-07&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Art of Product Design&lt;/em&gt; :: Hardi Meybaum&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#ender"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-06-02&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Speaker for the Dead&lt;/em&gt; :: Orson Scott Card&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#ender"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-06-01&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Ender's Game&lt;/em&gt; :: Orson Scott Card&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#effortless"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-05-26&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Effortless&lt;/em&gt; :: Greg McKeown&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#fifth-season"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-05-26&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Fifth Season&lt;/em&gt; :: N. K. Jemisin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#distrust"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-05-13&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Distrust that Particular Flavor&lt;/em&gt; :: William Gibson&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#player-games"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-05-09&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Player of Games&lt;/em&gt; :: Iain M. Banks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#bears-fire"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-05-03&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Bears Discover Fire and Other Stories&lt;/em&gt; :: Terry Bisson&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#phlebas"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-05-01&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Consider Phlebas&lt;/em&gt; :: Iain M. Banks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#synthesis-form"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-04-26&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://bookshop.org/a/100436/9780674627512"&gt;&lt;em&gt;Notes on the Synthesis of Form&lt;/em&gt; :: Christopher Alexander&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#lawns-meadows"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-04-25&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Lawns into Meadows&lt;/em&gt; :: Owen Wormster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★&lt;/td&gt;
&lt;td&gt;&lt;a href="#court-thorns-roses"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-04-18&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;A Court of Thorns and Roses&lt;/em&gt; :: Sarah J. Maas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#maniac"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-04-07&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The MANIAC&lt;/em&gt; :: Benjamin Labatut&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#midnight-library"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-03-24&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Midnight Library&lt;/em&gt; :: Matt Haig&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#birnam-wood"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-03-22&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Birnam Wood&lt;/em&gt; :: Eleanor Catton&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#night-circus"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-03-19&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Night Circus&lt;/em&gt; :: Erin Morgenstein&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#nine-princes"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-03-18&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Nine Princes in Amber&lt;/em&gt; :: Roger Zelazny&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="#little-big"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-03-12&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Little, Big&lt;/em&gt; :: John Crowley&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#soil-manual"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-03-11&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;A Soil Owner's Manual&lt;/em&gt; :: Jon Stika&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#dispossessed"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-03-10&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Dispossessed&lt;/em&gt; :: Ursula K. Le Guin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#book-thief"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-03-07&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Book Thief&lt;/em&gt; :: Markus Zusak&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#paved"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-03-05&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Paved Paradise&lt;/em&gt; :: Henry Grabar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#colt"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-03-03&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Revolver: Sam Colt and the Six-Shooter That Changed America&lt;/em&gt; :: Jim Rasenberger, Jacques Roy, et al.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#what-do-you-care"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-02-29&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;What Do You Care What Other People Think?&lt;/em&gt; :: Richard P. Feynman&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#conway"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-02-29&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Genius at Play: The Curious Mind of John Horton Conway&lt;/em&gt; :: Siobhan Roberts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#garp"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-02-25&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://bookshop.org/a/100436/9780593186879"&gt;&lt;em&gt;The World According to Garp&lt;/em&gt; :: John Irving&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#educated"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-02-20&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://bookshop.org/a/100436/9780399590528"&gt;&lt;em&gt;Educated&lt;/em&gt; :: Tara Westover&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#upcycle"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-02-18&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Upcycle&lt;/em&gt; :: William McDonough and Michael Braungart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#here-to-eternity"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-02-17&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;From Here to Eternity&lt;/em&gt; :: &lt;em&gt;Caitlin Doughty&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★&lt;/td&gt;
&lt;td&gt;&lt;a href="#among-others"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-02-17&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Among Others&lt;/em&gt; :: &lt;em&gt;Jo Walton&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#faster"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-02-09&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Faster&lt;/em&gt; :: &lt;em&gt;James Gleick&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#mr-feynman"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-02-05&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Surely You're Joking, Mr. Feynman!&lt;/em&gt; :: &lt;em&gt;Richard Feynman&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#be-alone"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-02-01&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;How to be Alone&lt;/em&gt; :: &lt;em&gt;Jonathen Franzen&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#becoming-yourself"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-01-29&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Although Of Course You End Up Becoming Yourself&lt;/em&gt; :: David Lipsky&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#nature-home"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-01-29&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Bringing Nature Home&lt;/em&gt; :: Douglas W. Tallamy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#salvation"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-01-27&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Salvation&lt;/em&gt; :: Peter F. Hamilton&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="#so-long"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-01-25&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;So Long, See You Tomorrow&lt;/em&gt; :: William Maxwell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#death-foretold"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-01-22&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Chronicle of a Death Foretold&lt;/em&gt; :: Gabriel García Márquez&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#the-information"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-01-20&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Information&lt;/em&gt; :: James Gleick&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#cradle-to-cradle"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-01-16&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Cradle to Cradle&lt;/em&gt; :: William McDonough and Michael Braungart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#life-of-ivan"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-01-13&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;One Day in the Life of Ivan Denisovich&lt;/em&gt; :: Aleksandr Solzhenitsyn&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#bad-weather"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-01-13&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;No Such Thing as Bad Weather&lt;/em&gt; :: Linda Åkeson McGurk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#hunt-gather-parent"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-01-13&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Hunt, Gather, Parent&lt;/em&gt; :: Michaeleen Doucleff&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#joy-of-x"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-01-10&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Joy of X&lt;/em&gt; :: Steven Strogatz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#rainbows-end"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-01-08&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Rainbow's End&lt;/em&gt; :: Vernor Vinge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#hour-of-the-star"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-01-06&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Hour of the Star&lt;/em&gt; :: Clarice Lispector&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#mt-char"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2024-01-03&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://bookshop.org/a/100436/9780553418620"&gt;&lt;em&gt;The Library at Mt. Char&lt;/em&gt; :: Scott Hawkins&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#tiffanys"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-12-31&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Breakfast at Tiffany's&lt;/em&gt; :: Truman Capote&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#robert-caro"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-12-30&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Working&lt;/em&gt; :: Robert A. Caro&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#moll-flanders"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-12-30&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Moll Flanders&lt;/em&gt; :: Daniel Dafoe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#gift-of-therapy"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-12-28&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Gift of Therapy&lt;/em&gt; :: Irvin Yalom&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#of-mice-and-men"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-12-27&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://bookshop.org/a/100436/9780140177398"&gt;&lt;em&gt;Of Mice and Men&lt;/em&gt; :: John Steinbeck&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#kitchen"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-12-26&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Kitchen&lt;/em&gt; :: Banana Yoshimoto&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#in-the-castle"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-12-24&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;We Have Always Lived In The Castle&lt;/em&gt; :: Shirley Jackson&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#radical-acceptance"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-12-21&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Radical Acceptance&lt;/em&gt; :: Tara Brach&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#turn-screw"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-12-19&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Turn of the Screw&lt;/em&gt; :: Henry James&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#train-dreams"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-12-12&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Train Dreams&lt;/em&gt; :: Denis Johnson&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#white-teeth"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-12-09&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://bookshop.org/a/100436/9780375703867"&gt;&lt;em&gt;White Teeth&lt;/em&gt; :: Zadie Smith&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#the-trial"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-11-26&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Trial&lt;/em&gt; :: Franz Kafka&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#lucky-jim"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-11-25&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Lucky Jim&lt;/em&gt; :: Kingsley Amis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★&lt;/td&gt;
&lt;td&gt;&lt;a href="#high-rise"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-11-25&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;High Rise&lt;/em&gt; :: J.G. Ballard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#things-fall-apart"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-11-25&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Things Fall Apart&lt;/em&gt; :: Chinua Achebe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#htwfaip"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-11-23&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;How to Win Friends and Influence People&lt;/em&gt; :: Dale Carnegie&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#strange"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-11-10&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Jonathan Strange &amp; Mr. Norrell&lt;/em&gt; :: Susanna Clarke&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#piranesi"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-11-07&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Piranesi&lt;/em&gt; :: Susanna Clarke&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#all-the-birds"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-10-26&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;All The Birds In The Sky&lt;/em&gt; :: Charlie Jane Anders&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#about-this"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-10-23&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;No One Is Talking About This&lt;/em&gt; :: Patricia Lockwood&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#east-of-eden"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-10-20&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://bookshop.org/a/100436/9780140186390"&gt;&lt;em&gt;East of Eden&lt;/em&gt; :: John Steinbeck&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#scientific-revolutions"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-10-12&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Structure of Scientific Revolutions&lt;/em&gt; :: Thomas S. Kuhn&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#the-body"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-10-10&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Body&lt;/em&gt; :: Bill Bryson&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#storm-front"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-10-03&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Storm Front&lt;/em&gt; :: Jim Butcher&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#men-machines"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-10-01&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://bookshop.org/a/100436/9780262529310"&gt;&lt;em&gt;Men, Machines, and Modern Times&lt;/em&gt; :: Elting E. Morison&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#breath"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-09-16&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Breath&lt;/em&gt; :: James Nestor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#oaks"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-09-18&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Nature of Oaks&lt;/em&gt; :: Douglas W. Tallamy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#writing-bones"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-09-18&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Writing Down the Bones&lt;/em&gt; :: Natalie Goldberg&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#moon-mistress"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-09-19&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Moon is a Harsh Mistress&lt;/em&gt; :: Robert A. Heinlein&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#timeless-way"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-09-20&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://bookshop.org/a/100436/9780195024029"&gt;&lt;em&gt;The Timeless Way of Building&lt;/em&gt; :: Christopher Alexander&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#accelerando"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-09-25&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Accelerando&lt;/em&gt; :: Charles Stross&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#permutation-city"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-09-28&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Permutation City&lt;/em&gt; :: Greg Egan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#no-head"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-09-29&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;On Having No Head&lt;/em&gt; :: Douglas Edison Harding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#things-fall-apart"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-09-15&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;When Things Fall Apart&lt;/em&gt; :: Pema Chödrön&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#walk-in-the-woods"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-09-14&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;A Walk in the Woods&lt;/em&gt; :: Bill Bryson&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#adult-children"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-09-12&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Adult Children of Emotionally Immature Parents&lt;/em&gt; :: Lindsay Gibson&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★&lt;/td&gt;
&lt;td&gt;&lt;a href="#see-the-sun"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-09-11&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;If You Could See The Sun&lt;/em&gt; :: Ann Liang&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#extra-virginity"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-09-10&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Extra Virginity&lt;/em&gt; :: Tom Mueller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#player-piano"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-09-09&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://bookshop.org/a/100436/9780385333788"&gt;&lt;em&gt;Player Piano&lt;/em&gt; :: Kurt Vonnegut&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#rest-ag"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-09-05&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Restoration Agriculture&lt;/em&gt; :: Mark Shepard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#no-logo"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-09-04&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;No Logo&lt;/em&gt; :: Naomi Klein&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#shadow-lost"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-09-02&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Shadow of What Was Lost&lt;/em&gt; :: James Islington&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#giant-hairball"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-09-01&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Orbiting the Giant Hairball&lt;/em&gt; :: Gordon MacKenzie&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#publicly-shamed"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-08-23&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;So You've Been Publicly Shamed&lt;/em&gt; :: Jon Ronson&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#left-hand-darkness"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-08-22&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Left Hand of Darkness&lt;/em&gt; :: Ursula K. Le Guin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#omnivores-dilemma"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-08-21&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://bookshop.org/a/100436/9780143038580"&gt;&lt;em&gt;The Omnivore's Dilemma&lt;/em&gt; :: Michael Pollan&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#everyday-things"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-08-18&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://bookshop.org/a/100436/9780465050659"&gt;&lt;em&gt;The Design of Everyday Things&lt;/em&gt; :: Don Norman&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#inner-game"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-08-12&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://bookshop.org/a/100436/9780679778318"&gt;&lt;em&gt;The Inner Game of Tennis&lt;/em&gt; :: W. Timothy Gallwey&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★&lt;/td&gt;
&lt;td&gt;&lt;a href="#triumph-of-seeds"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-08-09&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Triumph of Seeds&lt;/em&gt; :: Thor Hanson&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#bird-by-bird"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-08-08&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Bird by Bird&lt;/em&gt; :: Anne Lamott&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#diamond-age"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-08-08&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://bookshop.org/a/100436/9780553380965"&gt;&lt;em&gt;The Diamond Age&lt;/em&gt; :: Neal Stephenson&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#zen-writing"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-08-02&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Zen in the Art of Writing&lt;/em&gt; :: Ray Bradbury&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★&lt;/td&gt;
&lt;td&gt;&lt;a href="#read-nonfiction"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-08-01&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;How to Read Nonfiction Like a Professor&lt;/em&gt; :: Thomas C. Foster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#first-law"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-07-29&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Last Argument of Kings&lt;/em&gt; :: Joe Abercrombie&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#first-law"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-07-24&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Before They Are Hanged&lt;/em&gt; :: Joe Abercrombie&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#first-law"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-07-23&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Blade Itself&lt;/em&gt; :: Joe Abercrombie&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★&lt;/td&gt;
&lt;td&gt;&lt;a href="#company-of-one"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-07-21&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Company of One&lt;/em&gt; :: Paul Jarvis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#fair-play"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-07-21&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Fair Play&lt;/em&gt; :: Eve Rodsky&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#station-11"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-07-19&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Station Eleven&lt;/em&gt; :: Emily St. John Mandel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#real-life"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-07-17&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;We Are Never Meeting in Real Life&lt;/em&gt; :: Samantha Irby&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#your-life"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-07-15&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Story of Your Life and Others&lt;/em&gt; :: Ted Chiang&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#kafka-shore"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-07-11&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Kafka on the Shore&lt;/em&gt; :: Haruki Murakami&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#ove"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-06-29&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;A Man Called Ove&lt;/em&gt; :: Fredrik Backman&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#paper-menagerie"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-06-25&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Paper Menagerie&lt;/em&gt; :: Ken Liu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="#white-noise"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-06-11&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;White Noise&lt;/em&gt; :: Don DeLillo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#grapes-of-wrath"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-06-09&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://bookshop.org/a/100436/9780143039433"&gt;&lt;em&gt;The Grapes of Wrath&lt;/em&gt; :: John Steinbeck&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#swim-pond-rain"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-06-08&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;A Swim in a Pond in the Rain&lt;/em&gt; :: George Saunders&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#black-prism"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-05-25&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Black Prism&lt;/em&gt; :: Brent Weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#lucifers-hammer"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-05-22&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Lucifer's Hammer&lt;/em&gt; :: Larry Niven and Jerry Pournelle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#time-war"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-05-17&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;This Is How You Lose the Time War&lt;/em&gt; :: Amal El-Mohtar and Max Gladstone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#rosie"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-05-15&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Rosie Project&lt;/em&gt; :: Graeme Simsion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#100-year-old-man"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-05-13&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The 100-Year-Old Man Who Climbed Out the Window and Disappeared&lt;/em&gt; :: Jonas Jonasson&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#breakfast-of-champions"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-05-11&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://bookshop.org/a/100436/9780385334204"&gt;&lt;em&gt;Breakfast of Champions&lt;/em&gt; :: Kurt Vonnegut&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#hitchhikers"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-05-10&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Hitchhiker's Guide to the Galaxy&lt;/em&gt; :: Douglas Adams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#baby-led-feeding"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-05-09&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Baby-Led Feeding&lt;/em&gt; :: Jenna Helwig&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#no-lies"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-05-09&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Tell Me No Lies&lt;/em&gt; :: Bader, Pearson, Schwartz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#atlas-of-the-heart"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-05-09&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Atlas of the Heart&lt;/em&gt; :: Brené Brown&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#exhalation"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-05-01&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://bookshop.org/a/100436/9781101972083"&gt;&lt;em&gt;Exhalation: Stories&lt;/em&gt; :: Ted Chiang&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#one-straw"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-04-26&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The One-Straw Revolution&lt;/em&gt; :: Masanobu Fukuoka&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#how-we-got-to-now"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-04-05&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://bookshop.org/a/100436/9781594633935"&gt;&lt;em&gt;How We Got To Now&lt;/em&gt; :: Steven Johnson&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#walking-man"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-04-03&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Walking Man&lt;/em&gt; :: Jiro Taniguchi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#wintringham-mystery"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-04-03&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Wintringham Mystery&lt;/em&gt; :: Anthony Berkeley&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#tomorrow"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-04-01&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://bookshop.org/a/100436/9780593321201"&gt;&lt;em&gt;Tomorrow, and Tomorrow, and Tomorrow&lt;/em&gt; :: Gabrielle Zevin&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#a-confederacy-of-dunces"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-03-27&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://bookshop.org/a/100436/9780802130204"&gt;&lt;em&gt;A Confederacy of Dunces&lt;/em&gt; :: John Kennedy Toole&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#bridget-joness-diary"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-03-26&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Bridget Jones's Diary&lt;/em&gt; :: Helen Fielding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#promise-of-blood"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-03-23&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Promise of Blood&lt;/em&gt; :: Brian McClellan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#anything-you-want"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-03-16&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://bookshop.org/a/100436/9781991153319"&gt;&lt;em&gt;Anything You Want&lt;/em&gt; :: Derek Sivers&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#learned-optimism"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-03-12&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Learned Optimism&lt;/em&gt; :: Martin E. P. Seligman&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#to-say-nothing-of-the-dog"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-03-12&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;To Say Nothing of the Dog&lt;/em&gt; :: Connie Willis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#get-in-trouble"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-03-07&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Get In Trouble&lt;/em&gt; :: Kelly Link&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#slade-house"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-03-02&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Slade House&lt;/em&gt; :: David Mitchell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#the-bell-jar"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-03-02&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Bell Jar&lt;/em&gt; :: Sylvia Plath&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#the-fall-of-hyperion"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-02-22&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Fall of Hyperion&lt;/em&gt; :: Dan Simmons&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#the-anthropocene-reviewed"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-02-13&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://bookshop.org/a/100436/9780525555247"&gt;&lt;em&gt;The Anthropocene Reviewed&lt;/em&gt; :: John Green&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#me-talk-pretty-one-day"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-02-13&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Me Talk Pretty One Day&lt;/em&gt; :: David Sedaris&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#zero-to-one"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-02-09&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Zero to One&lt;/em&gt; :: Peter Thiel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#artemis"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-02-05&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Artemis&lt;/em&gt; :: Andy Weir&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#hyperion"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-02-02&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Hyperion&lt;/em&gt; :: Dan Simmons&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#the-war-of-art"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-01-27&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The War of Art&lt;/em&gt; :: Steven Pressfield&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#gods-debris"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-01-26&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;God's Debris&lt;/em&gt; :: Scott Adams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#interview-with-the-vampire"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-01-24&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Interview with the Vampire&lt;/em&gt; :: Anne Rice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#norwegian-wood"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-01-21&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://bookshop.org/a/100436/9780375704024"&gt;&lt;em&gt;Norwegian Wood&lt;/em&gt; :: Haruki Murakami&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#the-way-of-kings"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2023-01-18&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Way of Kings&lt;/em&gt; :: Brandon Sanderson&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#the-handmaids-tale"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2022-12-25&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://bookshop.org/a/100436/9780385490818"&gt;&lt;em&gt;The Handmaid's Tale&lt;/em&gt; :: Margaret Atwood&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#animal-farm"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2022-12-19&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Animal Farm&lt;/em&gt; :: George Orwell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#the-colour-of-magic"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2022-12-04&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Colour of Magic (Discworld #1)&lt;/em&gt; :: Terry Pratchett&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#the-remains-of-the-day"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2022-11-27&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Remains of the Day&lt;/em&gt; :: Kazuo Ishiguro&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#the-stranger"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2022-11-19&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;The Stranger&lt;/em&gt; :: Albert Camus&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#metamorphosis"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2022-11-13&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://bookshop.org/a/100436/9781680920727"&gt;&lt;em&gt;Metamorphosis&lt;/em&gt; :: Franz Kafka&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#discipline-is-destiny"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2022-10-30&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Discipline is Destiny&lt;/em&gt; :: Ryan Holiday&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#ubik"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2022-10-16&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Ubik&lt;/em&gt; :: Phillip K. Dick&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#triggers"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2022-10-09&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://bookshop.org/a/100436/9780804141239"&gt;&lt;em&gt;Triggers&lt;/em&gt; :: Marshall Goldsmith&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#book-of-the-new-sun"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2022-10-02&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;Book of the New Sun&lt;/em&gt; :: Gene Wolf&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#solaris"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2022-09-02&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://bookshop.org/a/100436/9780156027601"&gt;&lt;em&gt;Solaris&lt;/em&gt; :: Stanislaw Lem&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td&gt;&lt;a href="#the-good-earth"&gt;review&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2022-08-18&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://bookshop.org/a/100436/9781982147174"&gt;&lt;em&gt;The Good Earth&lt;/em&gt; :: Pearl S. Buck&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2 id="zen-happiness"&gt;Zen and the Art of Happiness&lt;/h2&gt;
&lt;p&gt;This book repeats itself: "convince yourself that you're living in the best
possible universe".&lt;/p&gt;
&lt;h2 id="prime-conspiracy"&gt;The Prime Number Conspiracy&lt;/h2&gt;
&lt;p&gt;Good variety of math journalism hits with that pleasing Quanta style.&lt;/p&gt;
&lt;h2 id="soulhome"&gt;Soulhome&lt;/h2&gt;
&lt;p&gt;My first foray into progression fantasy. Cool magic system propping up a weak
plot.&lt;/p&gt;
&lt;h2 id="good-inside"&gt;Good Inside&lt;/h2&gt;
&lt;p&gt;Dr. Becky is the best! Great guide to navigating big emotions in parenting and
life.&lt;/p&gt;
&lt;h2 id="factfulness"&gt;Factfulness&lt;/h2&gt;
&lt;p&gt;A great case that the world is getting better and that human intuition is
broken.&lt;/p&gt;
&lt;h2 id="give-and-take"&gt;Give and Take&lt;/h2&gt;
&lt;p&gt;Unremarkable inspo porn.&lt;/p&gt;
&lt;h2 id="bullshit"&gt;Bullshit&lt;/h2&gt;
&lt;p&gt;Surprisingly deep philosophical musings on BS.&lt;/p&gt;
&lt;h2 id="elegant-puzzle"&gt;An Elegant Puzzle&lt;/h2&gt;
&lt;p&gt;A collection of concrete wisdom about engineering management.&lt;/p&gt;
&lt;h2 id="how-innovation-works"&gt;How Innovation Works&lt;/h2&gt;
&lt;p&gt;This book traces the paths of how tinkerers improve ideas. I think I preferred
Isaacson's &lt;em&gt;Innovators&lt;/em&gt;, which illustrated many of the same ideas more clearly.&lt;/p&gt;
&lt;h2 id="musashi"&gt;Musashi&lt;/h2&gt;
&lt;p&gt;Epic.&lt;/p&gt;
&lt;h2 id="mom-test"&gt;The Mom Test&lt;/h2&gt;
&lt;p&gt;Brilliant guide to validating ideas.&lt;/p&gt;
&lt;h2 id="motivational-interviewing"&gt;Motivational Interviewing&lt;/h2&gt;
&lt;p&gt;This book contains life-changing strategies for active listening.&lt;/p&gt;
&lt;h2 id="golden-compass"&gt;The Golden Compass&lt;/h2&gt;
&lt;p&gt;Awesome. I somehow missed reading this as a child, but it's filled with depth
for adults.&lt;/p&gt;
&lt;h2 id="selfish-reasons"&gt;Selfish Reasons to Have More Kids&lt;/h2&gt;
&lt;p&gt;This book makes an excellent case that most people should consider having more
kids. In many ways, the central argument is "Kids make people happier. Most
parents spend way too much time on individual children. Nature will eventually
dominate your nurture, so nurture less and make more nature."&lt;/p&gt;
&lt;p&gt;It's written by an economist; the research was comprehensive and convincing. The
parenting advice was, uh, obviously written by an econimist.&lt;/p&gt;
&lt;h2 id="three-laws-of-nature"&gt;Three Laws of Nature&lt;/h2&gt;
&lt;p&gt;Potent primer on energy &amp;amp; entropy. Very focused, no filler. Highly recommended.&lt;/p&gt;
&lt;h2 id="sirens-muses"&gt;Sirens &amp;amp; Muses&lt;/h2&gt;
&lt;p&gt;A book about art students that seems to both idolize and resent its subject
matter. I suspect I would've enjoyed this book more if I were an actual artist.&lt;/p&gt;
&lt;h2 id="creation-lake"&gt;Creation Lake&lt;/h2&gt;
&lt;p&gt;There is so much to love about this story. The Neandrathal stuff was very
thought-provoking. Unfortunately, the plot lost way too much momentum in its
midsection, and the payoffs were not sufficiently setup to my tastes.&lt;/p&gt;
&lt;h2 id="ministry-of-time"&gt;Ministry of Time&lt;/h2&gt;
&lt;p&gt;Charming book. I would've preferred a short story.&lt;/p&gt;
&lt;h2 id="complexity"&gt;Complexity: A Guided Tour&lt;/h2&gt;
&lt;p&gt;A scattered (but very entertaining) survey of complex systems. Sometimes felt
like reading a "greatest hits" of all the coolest ideas in math/science, and
that's okay.&lt;/p&gt;
&lt;h2 id="supercommunicators"&gt;Supercommunicators&lt;/h2&gt;
&lt;p&gt;It's like HTWFAIP written by Malcolm Gladwell! And I really mean that in the
best possible way -- I thoroughly enjoyed this book, and feel like I learned a
lot from it.&lt;/p&gt;
&lt;h2 id="evil-politicians"&gt;How Evil Are Politicians?&lt;/h2&gt;
&lt;p&gt;Too much Bryan Caplan for me!&lt;/p&gt;
&lt;h2 id="spiders-war"&gt;The Spider's War&lt;/h2&gt;
&lt;p&gt;Satisfying conclusion to a fantastic series.&lt;/p&gt;
&lt;h2 id="case-against-education"&gt;The Case Against Education&lt;/h2&gt;
&lt;p&gt;This book mixes flamboyant hubris with intellectual humility -- I think I
actually quite enjoy those flavors together: strong opinions, supported with
meticulous evidence, but hedged with solid arguments against your case.&lt;/p&gt;
&lt;p&gt;But is education really "80% signaling"? Yeah, I think I'm pretty convinced of
that. But still unsure what to do with that information though. If it's true,
there's a vast and undervalued market gap for talent search/certification.&lt;/p&gt;
&lt;h2 id="radical-abundance"&gt;Radical Abundance&lt;/h2&gt;
&lt;p&gt;Decent crash course into atomically-precise manufacturing. Great frameworks for
thinking at small scales. Drexler seems to have a beef with the science
community, which felt like a constant distraction from the brilliant ideas in
the book.&lt;/p&gt;
&lt;h2 id="how-to-talk"&gt;How to Talk So Kids Will Listen &amp;amp; Listen So Kids Will Talk&lt;/h2&gt;
&lt;p&gt;This book really should be called "How to Talk So &lt;em&gt;People&lt;/em&gt; Will Listen". It's
loaded with actionable advice that I've already started using with my family.&lt;/p&gt;
&lt;h2 id="widows-house"&gt;The Widow's House&lt;/h2&gt;
&lt;p&gt;Okay, I really just need to know what happens next. Good writing, great plot,
but just a lot of it.&lt;/p&gt;
&lt;h2 id="naked-singularity"&gt;A Naked Singularity&lt;/h2&gt;
&lt;p&gt;A "perfect" book. Unbelievably, indescribably good. It's hard not to compare it
to DFW's fiction; both the writing style and its subject matter evoked &lt;em&gt;Infinite
Jest&lt;/em&gt; and &lt;em&gt;Broom of the System&lt;/em&gt;, and -- blaspheme incoming -- I much preferred
&lt;em&gt;A Naked Singularity&lt;/em&gt;. The plot, the themes, the characters, the feels, the
research -- what an experience!&lt;/p&gt;
&lt;h2 id="theory-of-oblivion"&gt;A General Theory of Oblivion&lt;/h2&gt;
&lt;p&gt;A potent literary tangle.&lt;/p&gt;
&lt;h2 id="impossible-man"&gt;The Impossible Man&lt;/h2&gt;
&lt;p&gt;This biography must've been exceptionally difficult to write. How does an author
explain generational harm without excusing it? How do they cover the pursuit of
genius without encouraging some of its most negative stereotypes?&lt;/p&gt;
&lt;h2 id="tyrants-law"&gt;The Tyrant's Law&lt;/h2&gt;
&lt;p&gt;I am loving this series! …and I want it to move faster.&lt;/p&gt;
&lt;h2 id="road"&gt;The Road&lt;/h2&gt;
&lt;p&gt;A book about life, death, and evil. It'll remain in your memory like a scar.
Highly recommended.&lt;/p&gt;
&lt;h2 id="prisoners-of-geography"&gt;Prisoners of Geography&lt;/h2&gt;
&lt;p&gt;An easy introduction to contemporary geopolitics. I wish I had read this in
middle school!&lt;/p&gt;
&lt;h2 id="kings-blood"&gt;The King's Blood&lt;/h2&gt;
&lt;p&gt;Great fantasy! Rarely do I read sequels, and even more rarely do I enjoy them
this much.&lt;/p&gt;
&lt;h2 id="cadillac-desert"&gt;Cadillac Desert&lt;/h2&gt;
&lt;p&gt;It's one of those non-fiction books that changes everything. Rarely do I read
something that needs to take such provocative measures to explain things that
should obvious and uncontroversial. Water is essential.&lt;/p&gt;
&lt;h2 id="welcome-bookshop"&gt;Welcome to the Hyanam-dong Bookshop&lt;/h2&gt;
&lt;p&gt;This book is the product of many positive social movements. While I didn't enjoy
the book's plot all that much, I'm very happy to see the zeitgeist carry us
here!&lt;/p&gt;
&lt;h2 id="thinking-with-type"&gt;Thinking with Type&lt;/h2&gt;
&lt;p&gt;A gorgeous, delightful, informative book. This work takes the art of printed
words to another level.&lt;/p&gt;
&lt;h2 id="quitting-social-media"&gt;James Acaster's Guide to Quitting Social Media&lt;/h2&gt;
&lt;p&gt;Acaster is funny, as usual; the book achieves some surprising depth through
absurdism.&lt;/p&gt;
&lt;h2 id="fab"&gt;Fab&lt;/h2&gt;
&lt;p&gt;A decent book about a powerful idea: the imminent personal fabrication
revolution. So hungry for more details/depth; I'll probably need to watch the
related "How to Make Almost Anything" course online.&lt;/p&gt;
&lt;h2 id="plow"&gt;Drive Your Plow Over the Bones of the Dead&lt;/h2&gt;
&lt;p&gt;Probably very good, but too much for me for now.&lt;/p&gt;
&lt;h2 id="tyll"&gt;Tyll&lt;/h2&gt;
&lt;p&gt;Nice, dark folklore.&lt;/p&gt;
&lt;h2 id="glucose-revolution"&gt;Glucose Revolution&lt;/h2&gt;
&lt;p&gt;Practical guide to managing blood sugar, backed by science.&lt;/p&gt;
&lt;h2 id="idiot"&gt;The Idiot&lt;/h2&gt;
&lt;p&gt;This novel is so detailed, it's hard to believe it's not a memoir. It's
sometimes too smart but overall beautifully written.&lt;/p&gt;
&lt;p&gt;I admittedly stopped reading this book after ~75% completion. The plot evoked
too many memories of longing, humiliation, regret, etc.&lt;/p&gt;
&lt;h2 id="dragons-path"&gt;The Dragon's Path&lt;/h2&gt;
&lt;p&gt;Excellent fantasy! I'd put it somewhere between Brandon Sanderson and George R R
Martin. Great characters, intriguing plot, well-crafted twists.&lt;/p&gt;
&lt;h2 id="oblivion-stories"&gt;Oblivion: Stories&lt;/h2&gt;
&lt;p&gt;To be honest, it's a bit too much David Foster Wallace. Most of the stories are
brilliant; some are extraordinary chores.&lt;/p&gt;
&lt;h2 id="dream-machine"&gt;The Dream Machine&lt;/h2&gt;
&lt;p&gt;An opinionated-yet-measured history of personal computing via the lens of JCR
Licklider, "Computing's Johnny Appleseed".&lt;/p&gt;
&lt;h2 id="heaven"&gt;Heaven&lt;/h2&gt;
&lt;p&gt;Cool exploration of good/evil via manic pixie dream girl. Clunky execution
overall.&lt;/p&gt;
&lt;h2 id="the-employees"&gt;The Employees&lt;/h2&gt;
&lt;p&gt;Objectification! Of androids, of humans, of women, of men, of employees, of
objects, etc. Very well done.&lt;/p&gt;
&lt;h2 id="convenience-store-woman"&gt;Convenience Store Woman&lt;/h2&gt;
&lt;p&gt;This tiny book made me feel a lot of things. It made me… wistful? kinder?
weirder? resentful?&lt;/p&gt;
&lt;h2 id="nexus"&gt;Nexus&lt;/h2&gt;
&lt;p&gt;In short: information is neutral, bureaucracy is interesting, and computers are
powerful. As usual, Harari is an excellent writer, but I felt somewhat
underwhelmed by this book. I suspect that this book was written to convince
other people of these things.&lt;/p&gt;
&lt;h2 id="capitalist-realism"&gt;Capitalist Realism&lt;/h2&gt;
&lt;p&gt;Makes a solid case that capitalism is responsible for much of the malaise and
misery of our era. I wish the book made clearer descriptions of alternatives to
capitalism. I understand that that was not the point of the book, but as the
book says, it's really hard to even picture a world without capitalism at this
point.&lt;/p&gt;
&lt;h2 id="no-country"&gt;No Country for Old Men&lt;/h2&gt;
&lt;p&gt;One of those rare books with incredible characters, beautiful (yet
unpretentious) prose, gripping plot, clever dialogue, and enough depth to drown
yourself.&lt;/p&gt;
&lt;h2 id="strangest"&gt;The Strangest Man&lt;/h2&gt;
&lt;p&gt;Cool biography about cool dude.&lt;/p&gt;
&lt;h2 id="trust"&gt;Trust&lt;/h2&gt;
&lt;p&gt;It's a metafictional novel about capital/power with a deeply compelling plot!
Wow!&lt;/p&gt;
&lt;h2 id="pastoralia"&gt;Pastoralia&lt;/h2&gt;
&lt;p&gt;One of the best fictional works I've ever encountered. Funny! Painful! Rich!
Otherworldly!&lt;/p&gt;
&lt;h2 id="atonement"&gt;Atonement&lt;/h2&gt;
&lt;p&gt;I generally have no patience for stories like this. I don't know how this
differs from other romance/WWII books, but, wow, &lt;em&gt;Atonement&lt;/em&gt; hits hard in all
the right ways at all the right times -- an expertly crafted plot.&lt;/p&gt;
&lt;h2 id="when-we-cease"&gt;When We Cease to Understand the World&lt;/h2&gt;
&lt;p&gt;I adore Labatut's willingness to take liberties with history; it's the perfect
amount of speculation injected into non-fiction. Stories of mad geniuses are
irresistible to me, and this book is full of such true(ish) tales.&lt;/p&gt;
&lt;h2 id="the-friend"&gt;The Friend&lt;/h2&gt;
&lt;p&gt;Well-written literary fiction, but nothing remarkable.&lt;/p&gt;
&lt;h2 id="hateship"&gt;Hateship, Friendship, Courtship, Loveship, Marriage&lt;/h2&gt;
&lt;p&gt;Tidy, enjoyable short stories.&lt;/p&gt;
&lt;h2 id="new-machine"&gt;The Soul of a Machine&lt;/h2&gt;
&lt;p&gt;A journalist lives in a basement with an engineering team for 18 months while
they attempt to build a 32-bit computer from scratch.&lt;/p&gt;
&lt;h2 id="seeing-state"&gt;Seeing Like a State&lt;/h2&gt;
&lt;p&gt;After reading &lt;em&gt;Guns, Germs, and Steel&lt;/em&gt;, I felt like I was suddenly endowed with
a unified framework for understanding history and geopolitics.&lt;/p&gt;
&lt;p&gt;For me, &lt;em&gt;Seeing Like a State&lt;/em&gt; has induced similar "framework thinking".
Governments behave for good reasons; they succeed/fail in predictable ways.&lt;/p&gt;
&lt;h2 id="stay-true"&gt;Stay True&lt;/h2&gt;
&lt;p&gt;It's a memoir. If you like memoirs, you'll like this memoir. It's about growing
up, finding identity, friendship, grief -- yep, it's a memoir.&lt;/p&gt;
&lt;h2 id="evicted"&gt;Evicted&lt;/h2&gt;
&lt;p&gt;An essential non-fiction read. Great storytelling; difficult to stomach at
times. Great insights into the struggles of both landlords and tenants.&lt;/p&gt;
&lt;h2 id="olive"&gt;Olive Kitteridge&lt;/h2&gt;
&lt;p&gt;Remarkably good short stories, with excellent and subtle commentary on that
tiresome brain defect that so many of us are born with.&lt;/p&gt;
&lt;h2 id="shining"&gt;A Shining&lt;/h2&gt;
&lt;p&gt;what on e*rth did I just read and why am I barefoot&lt;/p&gt;
&lt;h2 id="outline"&gt;Outline&lt;/h2&gt;
&lt;p&gt;Plotless, yet gripping! Somehow reads like second-person POV but obviously
isn't. A book that takes listening to oneself and others very seriously.&lt;/p&gt;
&lt;h2 id="glass-cage"&gt;The Glass Cage&lt;/h2&gt;
&lt;p&gt;Automation good! Automation bad!&lt;/p&gt;
&lt;p&gt;I found Christian's &lt;em&gt;Alignment Problem&lt;/em&gt; and Kuang's &amp;amp; Fabricant's
&lt;em&gt;User-Friendly&lt;/em&gt; more enlightening on this same topic.&lt;/p&gt;
&lt;h2 id="volt-rush"&gt;Volt Rush&lt;/h2&gt;
&lt;p&gt;Feels like a timeline that I neither understand nor care about is being forcibly
spoonfed to me. I think a commodity-traders narrative (a la &lt;em&gt;The World for
Sale&lt;/em&gt;), a Michael-Pollan-esque story, or a land-based "first-principles"
approach (a la &lt;em&gt;Guns, Germs, and Steel&lt;/em&gt;) would've been much more interesting.&lt;/p&gt;
&lt;h2 id="demon-copperhead"&gt;Demon Copperhead&lt;/h2&gt;
&lt;p&gt;Reads like a modern "hillbilly" Anna Karenina (or maybe something more deep and
heavy). This book will forever alter the way I see rural America, opioid
addiction, and systemic oppression.&lt;/p&gt;
&lt;h2 id="world-sale"&gt;The World for Sale&lt;/h2&gt;
&lt;p&gt;Top-tier historical account of the wide-reaching influence of commodities and
their traders.&lt;/p&gt;
&lt;h2 id="roadside-picnic"&gt;Roadside Picnic&lt;/h2&gt;
&lt;p&gt;Read it! It's so, so, so good. The plot, the writing, the characters, the ideas
-- it's brilliant.&lt;/p&gt;
&lt;h2 id="war-below"&gt;The War Below&lt;/h2&gt;
&lt;p&gt;The book somehow moves both too quickly and too slowly for me to absorb its
contents.&lt;/p&gt;
&lt;h2 id="frostbite"&gt;Frostbite&lt;/h2&gt;
&lt;p&gt;Refrigeration is cool! I salute the author for a worthy tilt at a difficult
topic.&lt;/p&gt;
&lt;h2 id="newts"&gt;War with the Newts&lt;/h2&gt;
&lt;p&gt;Peculiar, potent, prescient.&lt;/p&gt;
&lt;h2 id="leviathan"&gt;Leviathan&lt;/h2&gt;
&lt;p&gt;Relentless melodrama with some clever character/plot structures.&lt;/p&gt;
&lt;h2 id="enchanted-objects"&gt;Enchanted Objcets&lt;/h2&gt;
&lt;p&gt;A refreshing plea to build delightful technology with fewer screens.&lt;/p&gt;
&lt;h2 id="long-earth"&gt;The Long Earth&lt;/h2&gt;
&lt;p&gt;One of the most charming and brilliant books I've ever experienced. I'm
awestruck by its scope, by its humor, and by its depth.&lt;/p&gt;
&lt;h2 id="alignment"&gt;The Alignment Problem&lt;/h2&gt;
&lt;p&gt;An excellent history of AI/ML in the context of human goals. If you're looking
for a lightly-technical book covering the trajectory of AI/ML, I highly
recommend this book.&lt;/p&gt;
&lt;h2 id="impossible-conversations"&gt;How to Have Impossible Conversations&lt;/h2&gt;
&lt;p&gt;A guide to exercising curiosity; lots of great tips on listening, suspending
judgement, maintaining common ground, and sowing doubt. These "applied
epistimology" folks might be onto something.&lt;/p&gt;
&lt;p&gt;My major takeaway is that knowing &lt;em&gt;how&lt;/em&gt; people believe is generally more useful
than knowing &lt;em&gt;why&lt;/em&gt; they believe.&lt;/p&gt;
&lt;h2 id="sellout"&gt;The Sellout&lt;/h2&gt;
&lt;p&gt;Insanely good lit. Humans will be laughing/crying with this book for centuries.&lt;/p&gt;
&lt;h2 id="slow-productivity"&gt;Slow Productivity&lt;/h2&gt;
&lt;p&gt;More of the same good Cal Newport advice. I highly recommend
&lt;a href="https://calnewport.com/podcasts/"&gt;his podcast&lt;/a&gt; if you want sane and sustainable
advice on how to achieve goals.&lt;/p&gt;
&lt;h2 id="quantations"&gt;Quantations: A Guide to Quantum Living in the 21st Century&lt;/h2&gt;
&lt;p&gt;I'M GOING QUANTUM!&lt;/p&gt;
&lt;h2 id="sparrow"&gt;The Sparrow&lt;/h2&gt;
&lt;p&gt;Thorough exploration of the problem of evil. A brutal tale about faith, science,
religion, and culture. The highs are very high and the lows are very low. The
cultural worldbuilding was entertaining, but detracted somewhat from the potent
narrative.&lt;/p&gt;
&lt;h2 id="co-intelligence"&gt;Co-Intelligence&lt;/h2&gt;
&lt;p&gt;A practical introduction to integrating AI into your everyday life. I'd
definitely recommend it to somebody who doesn't understand how/why ChatGPT is
useful. But to me, it was just preaching to the proverbial choir.&lt;/p&gt;
&lt;h2 id="politics-of-design"&gt;The Politics of Design&lt;/h2&gt;
&lt;p&gt;An appealing fisheye journey through design.&lt;/p&gt;
&lt;h2 id="the-overstory"&gt;The Overstory&lt;/h2&gt;
&lt;p&gt;The first ~20% of The Overstory is delightful -- short stories where entire
human lifetimes breeze by in a few pages.&lt;/p&gt;
&lt;p&gt;But then the author tries to weave together the individual tales (a la
Seinfeld?) and completely fumbles the pacing, the characters, and everything I
so liked about the introduction.&lt;/p&gt;
&lt;p&gt;In the end, it feels like an Ayn Rand's &lt;em&gt;Cloud Atlas&lt;/em&gt; about logging. Seems like
something that Netflix would try to turn into a series and then cancel it three
seasons in.&lt;/p&gt;
&lt;h2 id="user-friendly"&gt;User Friendly&lt;/h2&gt;
&lt;p&gt;Good overview of human-centered design in engineering. A surprising amount of
anecdotes I hadn't heard before.&lt;/p&gt;
&lt;h2 id="cold-start"&gt;The Cold Start Problem&lt;/h2&gt;
&lt;p&gt;Solid strategic guide for any startup building on networks. Lots of fantastic
idea fodder.&lt;/p&gt;
&lt;h2 id="going-infinite"&gt;Going Infinite&lt;/h2&gt;
&lt;p&gt;An entertaining biography! I'm curious how the EA community and other proximate
parties would tell the story.&lt;/p&gt;
&lt;h2 id="build"&gt;Build&lt;/h2&gt;
&lt;p&gt;A solid startup guide borne from an impressive career.&lt;/p&gt;
&lt;h2 id="rise-of-td"&gt;The Rise of Theodore Roosevelt&lt;/h2&gt;
&lt;p&gt;The definitive story of the ascent of one of planet Earth's most interesting
creatures.&lt;/p&gt;
&lt;p&gt;When I was much younger, this book sparked my lifelong pursuit of excitement and
power. My lust for bustle persists, much to everybody's chagrin.&lt;/p&gt;
&lt;h2 id="grabcad"&gt;The Art of Product Design&lt;/h2&gt;
&lt;p&gt;An entertaining sales pitch hardly disguised as a book.&lt;/p&gt;
&lt;h2 id="ender"&gt;Ender's Game saga&lt;/h2&gt;
&lt;p&gt;I think I'm a sucker for "lonely messiah-complex mastermind" stories, and that
is not a good thing. I never thought I'd fall prey to
&lt;a href="https://peachfront.diaryland.com/enderhitlte.html"&gt;sympathizing with Superman&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Speaker for the Dead&lt;/em&gt; is especially good. It's like
&lt;a href="https://hyperioncantos.fandom.com/wiki/Cruciform"&gt;the cruciform arc&lt;/a&gt; coauthored
by Borges and Gabriel García Márquez. But again, written by
&lt;a href="https://www.salon.com/2013/03/07/end_game_for_orson_scott_card_partner/"&gt;a problematic person&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="effortless"&gt;Effortless&lt;/h2&gt;
&lt;p&gt;Nothing remarkable; this book was a pleasant reminder to stop trying so dang
hard.&lt;/p&gt;
&lt;h2 id="fifth-season"&gt;The Fifth Season&lt;/h2&gt;
&lt;p&gt;Very, very good sci-fi/fantasy writing. It's filled with
provocative-but-not-preachy themes, which is the hallmark of good sci-fi.
Jemisin is unbelievably good at writing characters and making their interactions
believable.&lt;/p&gt;
&lt;p&gt;One thing I realized from this book is that I may have exhausted my patience for
high-fantasy worldbuilding. Sometimes these fantasy books feel like taking a
sociology class for fictional cultures, which is expected in the sci-fi/fantasy
genre, but incompatible with my shrinking attention span.&lt;/p&gt;
&lt;h2 id="distrust"&gt;Distrust that Particular Flavor&lt;/h2&gt;
&lt;p&gt;A scattered-but-potent collection of non-fiction writings from a founding father
of cyberpunk.&lt;/p&gt;
&lt;h2 id="player-games"&gt;The Player of Games&lt;/h2&gt;
&lt;p&gt;Clever sci-fi that desparately wants you to know how clever it is.&lt;/p&gt;
&lt;h2 id="bears-fire"&gt;Bears Discover Fire and Other Stories&lt;/h2&gt;
&lt;p&gt;Criminally underrated short story collection from the author of
&lt;a href="https://www.mit.edu/people/dpolicar/writing/prose/text/thinkingMeat.html"&gt;They're Made Out Of Meat&lt;/a&gt;.
Clever, deep, and human.&lt;/p&gt;
&lt;h2 id="phlebas"&gt;Consider Phlebas&lt;/h2&gt;
&lt;p&gt;Well I finally started The Culture series. The first book had excellent sci-fi
worldbuilding sandwiched between a lame action/adventure plot and bad writing. I
suspect that Banks would be incredible at writing short stories, where his ideas
could maintain focus.&lt;/p&gt;
&lt;h2 id="synthesis-form"&gt;Notes on the Synthesis of Form&lt;/h2&gt;
&lt;p&gt;One of the most profound and underrated works of the twentieth century.&lt;/p&gt;
&lt;p&gt;In this short book, Christopher Alexander does for design(?) what Newton did for
physics -- he wrestled a whole field out of the realm of the feelings and gods
into the domain of math and mankind.&lt;/p&gt;
&lt;h2 id="lawns-meadows"&gt;Lawns into Meadows&lt;/h2&gt;
&lt;p&gt;Felt like reading a very good blog post.&lt;/p&gt;
&lt;h2 id="court-thorns-roses"&gt;A Court of Thorns and Roses&lt;/h2&gt;
&lt;p&gt;Solid writing marred by unbearable pacing.&lt;/p&gt;
&lt;h2 id="maniac"&gt;The MANIAC&lt;/h2&gt;
&lt;p&gt;A one-of-a-kind book. Felt like a (1) BBC radio drama covering (2) the mad
scientists of WWII with a (3) brilliant throughline to today.&lt;/p&gt;
&lt;h2 id="midnight-library"&gt;The Midnight Library&lt;/h2&gt;
&lt;p&gt;Reminded me of Mitch Albom, Groundhog Day, and maybe Black Mirror… in a good
way.&lt;/p&gt;
&lt;h2 id="birnam-wood"&gt;Birnam Wood&lt;/h2&gt;
&lt;p&gt;The author is skilled at writing believable Sorkin-esque monologues from a ton
of different political perspectives. She's also got a talent for setting up a
house of cards and knocking it down with style. But I don't know -- I didn't
enjoy the story.&lt;/p&gt;
&lt;h2 id="night-circus"&gt;The Night Circus&lt;/h2&gt;
&lt;p&gt;Captivating plot, likable characters, cool magic, fantastic imagery. It's
surprising that Hollywood hasn't made an adaptation yet.&lt;/p&gt;
&lt;h2 id="nine-princes"&gt;Nine Princes in Amber&lt;/h2&gt;
&lt;p&gt;Begins as a nifty amnesiac action-thriller and then slowly morphs into typical
renfair male fantasy.&lt;/p&gt;
&lt;h2 id="little-big"&gt;Little, Big&lt;/h2&gt;
&lt;p&gt;Extraordinary modern fairy tale with lovely prose. A one-of-a-kind book. Also
somewhat boring.&lt;/p&gt;
&lt;h2 id="soil-manual"&gt;A Soil Owner's Manual&lt;/h2&gt;
&lt;p&gt;Neat little book about soil health.&lt;/p&gt;
&lt;h2 id="dispossessed"&gt;The Dispossessed&lt;/h2&gt;
&lt;p&gt;Marvelous storytelling and worthwhile thought-experiments for
anarchist/communist/capitalist sympathizers.&lt;/p&gt;
&lt;h2 id="book-thief"&gt;The Book Thief&lt;/h2&gt;
&lt;p&gt;The author is clearly very talented, but this story was underwhelming. Reads
like the worst kind of high-school-English-required-reading.&lt;/p&gt;
&lt;h2 id="paved"&gt;Paved Paradise&lt;/h2&gt;
&lt;p&gt;The author makes a surprisingly bipartisan case against parking. He summarizes
his recommendations as follows:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;The path forward from a policy perspective seemed clear. Abolish parking
  minimums and let developers build the amount of parking their clients want.
  Break garage rents apart from apartment rents so carless tenants don't have to
  subsidize their neighbors driving. Recognize that more parking means less
  housing, especially affordable housing. Let different uses -- an office and an
  apartment building, a school and a movie theater -- share parking. Charge for
  the best street parking, and use parking prices and enforcement not to
  generate cash and cycles of punishment but to manage city streets. Invest the
  proceeds in the neighborhood. Let architects design environments where people
  can walk. Ask drivers to bear some of the externalities of automobile use.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="colt"&gt;Revolver: Sam Colt and the Six-Shooter That Changed America&lt;/h2&gt;
&lt;p&gt;I read this book for specific research purposes. I commend the authors for
striking a nice balance between entertainment and historical neutrality.&lt;/p&gt;
&lt;h2 id="what-do-you-care"&gt;What Do You Care What Other People Think?&lt;/h2&gt;
&lt;p&gt;Surely your publishers want bonus content, Mr. Feynman!&lt;/p&gt;
&lt;h2 id="conway"&gt;Genius at Play&lt;/h2&gt;
&lt;p&gt;I hate reading biographies, but this one was alright. Conway was a character!&lt;/p&gt;
&lt;h2 id="garp"&gt;The World According to Garp&lt;/h2&gt;
&lt;p&gt;A book seemingly about death &amp;amp; male feminism, and also the act of writing about
death &amp;amp; male feminism. This book started out very funny and then slowly became
an artistic chore (in a good way, I guess). A
sometimes-annoying-but-admittedly-brilliant work of metafiction.&lt;/p&gt;
&lt;h2 id="educated"&gt;Educated&lt;/h2&gt;
&lt;p&gt;It's like &lt;em&gt;Little House on the Prairie&lt;/em&gt;, but with graphic abuse and trauma. I
wish it wasn't a true story. Sharp words; lasting scars. Unforgettable strength.&lt;/p&gt;
&lt;h2 id="upcycle"&gt;The Upcycle&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Cradle to Cradle&lt;/em&gt; errata and bonus content.&lt;/p&gt;
&lt;h2 id="here-to-eternity"&gt;From Here to Eternity&lt;/h2&gt;
&lt;p&gt;Enlightening survey of problems in the funeral industry, with plenty of good
comparisons to historical/worldwide customs. Corpses are not to be feared.&lt;/p&gt;
&lt;h2 id="among-others"&gt;Among Others&lt;/h2&gt;
&lt;p&gt;Insufferable mix of revisionist-history-memoir, magical realism, and
misunderstood bookworm heroine. I know it received great reviews, but I just
didn't understand the hype.&lt;/p&gt;
&lt;h2 id="faster"&gt;Faster&lt;/h2&gt;
&lt;p&gt;Standard Gleick deep-dive into human-scale time/speed. Lots of good insights on
efficiency and "average" time.&lt;/p&gt;
&lt;h2 id="mr-feynman"&gt;Surely You're Joking, Mr. Feynman!&lt;/h2&gt;
&lt;p&gt;Classic rascal tales!&lt;/p&gt;
&lt;h2 id="be-alone"&gt;How to be Alone&lt;/h2&gt;
&lt;p&gt;Smart essays for smart people. Not for me.&lt;/p&gt;
&lt;h2 id="becoming-yourself"&gt;Although Of Course You End Up Becoming Yourself&lt;/h2&gt;
&lt;p&gt;It's a transcript of a very intimate David Foster Wallace interview, where David
Lipsky spends lots of time with Wallace during the final leg of his ascent to
infamy. I can't imagine anybody doing as good of a job as Lipsky here; he really
knew when to let David talk, and how to highlight the scenery without letting it
steal focus from the main show.&lt;/p&gt;
&lt;p&gt;It was uncanny to hear DFW voice so many of my own sticky/ineffable anxieties
about addiction, fame, media, intelligence, etc. His ability to point at those
difficult-to-articulate feelings is probably why he is considered one of the
greats.&lt;/p&gt;
&lt;h2 id="nature-home"&gt;Bringing Nature Home&lt;/h2&gt;
&lt;p&gt;Dry book about plant diversity. Would be nice as a 90-minute lecture, but a
grueling experience in book form.&lt;/p&gt;
&lt;h2 id="salvation"&gt;Salvation&lt;/h2&gt;
&lt;p&gt;Starts out as world-class sci-fi and then devolves into cliché. Personally, this
book smelled like an incredibly talented author let down by a lack of support
from their editor.&lt;/p&gt;
&lt;h2 id="so-long"&gt;So Long, See You Tomorrow&lt;/h2&gt;
&lt;p&gt;I was completely unsure how to feel about this short work. Such moving
storytelling, but at the same time, I felt so distant.&lt;/p&gt;
&lt;h2 id="death-foretold"&gt;Chronicle of a Death Foretold&lt;/h2&gt;
&lt;p&gt;Charming story about a rube-goldberg machine drama in a tiny town.&lt;/p&gt;
&lt;h2 id="the-information"&gt;The Information&lt;/h2&gt;
&lt;p&gt;A history of information theory for the layman. Lots of good tidbits.&lt;/p&gt;
&lt;h2 id="cradle-to-cradle"&gt;Cradle to Cradle&lt;/h2&gt;
&lt;p&gt;A refreshingly holistic approach to sustainability! Solid foundations on
real-world chemistry. Although pretentious in many parts, the authors have
receipts to back up their bold opinions.&lt;/p&gt;
&lt;h2 id="life-of-ivan"&gt;One Day in the Life of Ivan Denisovich&lt;/h2&gt;
&lt;p&gt;Expertly crafted book about a very not fun day at the Russian labor camp. While
historically significant and well-written, it was hard for me to relate to any
of it.&lt;/p&gt;
&lt;h2 id="bad-weather"&gt;No Such Thing as Bad Weather&lt;/h2&gt;
&lt;p&gt;Lots of gems, but much too wordy. Children play. Outside good. Cold okay.
Scandinavia much advanced.&lt;/p&gt;
&lt;h2 id="hunt-gather-parent"&gt;Hunt, Gather, Parent&lt;/h2&gt;
&lt;p&gt;A fantastic piece of journalism; full of bold, testable, and convincing claims.
This book has already made an impact on how I'm choosing to parent my daughter.&lt;/p&gt;
&lt;h2 id="joy-of-x"&gt;The Joy of X&lt;/h2&gt;
&lt;p&gt;Nifty little chapterized overview of mathematics. Lots of fun examples and
"greatest hits".&lt;/p&gt;
&lt;h2 id="rainbows-end"&gt;Rainbow's End&lt;/h2&gt;
&lt;p&gt;Lots of neat futurism ideas with a plot stapled to it.&lt;/p&gt;
&lt;h2 id="hour-of-the-star"&gt;The Hour of the Star&lt;/h2&gt;
&lt;p&gt;It defies explanation. Just the right amount of crazy -- hardtack biscuits with
chocolate chips.&lt;/p&gt;
&lt;h2 id="mt-char"&gt;The Library at Mt. Char&lt;/h2&gt;
&lt;p&gt;Intricate fantasy/sci-fi/horror with great insights into
power/god/evil/parenting.&lt;/p&gt;
&lt;h2 id="tiffanys"&gt;Breakfast at Tiffany's&lt;/h2&gt;
&lt;p&gt;Fun/sad little story with lots to love.&lt;/p&gt;
&lt;h2 id="robert-caro"&gt;Working: Researching, Interviewing, Writing&lt;/h2&gt;
&lt;p&gt;After listening to
&lt;a href="https://99percentinvisible.org/episode/breaking-down-the-power-broker/"&gt;Conan O'Brien gush over Robert Caro on a design podcast&lt;/a&gt;,
my curiosity was piqued. After reading this essay collection, I'm now fully
convinced that Caro is a national treasure.&lt;/p&gt;
&lt;h2 id="moll-flanders"&gt;Moll Flanders&lt;/h2&gt;
&lt;p&gt;This book reads like 17th century
&lt;a href="https://en.wikipedia.org/wiki/Domestic_Girlfriend"&gt;&lt;em&gt;Domestic Girlfriend&lt;/em&gt;&lt;/a&gt;. The
first half of the book has plenty of juicy twists, but the plot becomes
repetetive very quickly.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Moll Flanders&lt;/em&gt; was likely progressive for its time, but its attempt at feminism
has dated poorly.&lt;/p&gt;
&lt;h2 id="gift-of-therapy"&gt;The Gift of Therapy&lt;/h2&gt;
&lt;p&gt;Solid collection of advice/notes from a career therapist.&lt;/p&gt;
&lt;h2 id="of-mice-and-men"&gt;Of Mice and Men&lt;/h2&gt;
&lt;p&gt;Tell me about the rabbits!&lt;/p&gt;
&lt;h2 id="kitchen"&gt;Kitchen&lt;/h2&gt;
&lt;p&gt;Excellent insights on the grieving process, i.e. good grief!&lt;/p&gt;
&lt;h2 id="in-the-castle"&gt;We Have Always Lived In The Castle&lt;/h2&gt;
&lt;p&gt;Sometimes slow and annoying, but overall very clever. The story overflows with
crucial details that are easy to overlook.&lt;/p&gt;
&lt;h2 id="radical-acceptance"&gt;Radical Acceptance&lt;/h2&gt;
&lt;p&gt;Solid spiritual advice with good storytelling.&lt;/p&gt;
&lt;h2 id="turn-screw"&gt;Turn of the Screw&lt;/h2&gt;
&lt;p&gt;Lackluster gothic horror with plenty of mystery but no fright or feeling.&lt;/p&gt;
&lt;h2 id="train-dreams"&gt;Train Dreams&lt;/h2&gt;
&lt;p&gt;Phrenetic, short, and somber.&lt;/p&gt;
&lt;h2 id="white-teeth"&gt;White Teeth&lt;/h2&gt;
&lt;p&gt;Well, the Zadie Smith hype is justified. &lt;em&gt;White Teeth&lt;/em&gt; is excellent. Solid
dialogue. Integrates multiple cultures into one narrative without obvious
&lt;a href="https://en.wikipedia.org/wiki/Tokenism"&gt;tokenism&lt;/a&gt;. Everything feels extremely
intentional (in a good way). Slow beginning, but worthwhile payoffs.&lt;/p&gt;
&lt;h2 id="the-trial"&gt;The Trial&lt;/h2&gt;
&lt;p&gt;A scattered plot with decent depth but generally dislikable characters. Funny at
many parts, but no stakes provided.&lt;/p&gt;
&lt;h2 id="lucky-jim"&gt;Lucky Jim&lt;/h2&gt;
&lt;p&gt;A book whose sole purpose seems to tarnish academia. Somewhat funny, but felt
hollow and unconstructive.&lt;/p&gt;
&lt;h2 id="high-rise"&gt;High Rise&lt;/h2&gt;
&lt;p&gt;An almost-interesting story premise embedded in shallow writing.&lt;/p&gt;
&lt;h2 id="things-fall-apart"&gt;Things Fall Apart&lt;/h2&gt;
&lt;p&gt;Solid fiction about tribal Africa. I liked most when underlying motivations for
cultural practices were hinted at or explored.&lt;/p&gt;
&lt;h2 id="htwfaip"&gt;How to Win Friends and Influence People&lt;/h2&gt;
&lt;p&gt;All the advice about human interaction that you should've learned (but didn't)
and need to be reminded of (but don't).&lt;/p&gt;
&lt;h2 id="strange"&gt;Jonathan Strange &amp;amp; Mr. Norrell&lt;/h2&gt;
&lt;p&gt;An enthralling and epic story, although sometimes a bit too British for my small
brain. I suspect this book would actually be much better as a television series.&lt;/p&gt;
&lt;h2 id="piranesi"&gt;Piranesi&lt;/h2&gt;
&lt;p&gt;A beautiful and unique labyrinth story.&lt;/p&gt;
&lt;h2 id="all-the-birds"&gt;All the Birds in the Sky&lt;/h2&gt;
&lt;p&gt;Heartwarming apocalyptic story about love, nature, magic, technology, growing
up, generational trauma, etc. Some of the romance felt forced. Wouldn't be
surprised to see it on the silver screen one day.&lt;/p&gt;
&lt;h2 id="about-this"&gt;No One Is Talking About This&lt;/h2&gt;
&lt;p&gt;This book was painful. Normally books about internet addiction don't feel
applicable to me because I never got sucked into Facebook or Twitter.&lt;/p&gt;
&lt;p&gt;But this book felt like a personal attack. The author created a creepy
caricature of the habits and values I once espoused. And her takes were
completely reasonable.&lt;/p&gt;
&lt;p&gt;I am impressed with how the author painted her political counterparts in a
reasonable light. The US's alt-left and alt-right seem to be equal products of
meme culture.&lt;/p&gt;
&lt;p&gt;I didn't care much for the plot. There were quite a few things that felt forced,
but they were easy to ignore amidst the excellent writing. All the story beats
are relatively predictable, but it doesn't lessen the blow.&lt;/p&gt;
&lt;h2 id="east-of-eden"&gt;East of Eden&lt;/h2&gt;
&lt;p&gt;Deeply human. Possibly the greatest book ever written.&lt;/p&gt;
&lt;h2 id="scientific-revolutions"&gt;The Structure of Scientific Revolutions&lt;/h2&gt;
&lt;p&gt;Dry but incredibly deep work on how paradigm shifts work in the
academic/scientific community.&lt;/p&gt;
&lt;h2 id="the-body"&gt;The Body&lt;/h2&gt;
&lt;p&gt;Excellent writing (as usual) from Bryson, but content felt both too broad and
too narrow at the same time. Recommended for middle-school students and adults
unfamiliar with human biology.&lt;/p&gt;
&lt;h2 id="storm-front"&gt;Storm Front&lt;/h2&gt;
&lt;p&gt;My new favorite soft-magic fantasy world… embedded in a horny detective
thriller. Imagine if Harry Potter was actually a relatively good story; instead
of a student, he was a detective trapped in a softcore smut novel. The story
would've been a masterpiece if the author swapped the ecchi stuff with some
actual character development.&lt;/p&gt;
&lt;h2 id="men-machines"&gt;Men, Machines, and Modern Times&lt;/h2&gt;
&lt;p&gt;The definitive essay collection covering technology/innovation/society. Filled
with lovely prose, accurate prophecies, great examples, and useful mental
models.&lt;/p&gt;
&lt;h2 id="no-head"&gt;On Having No Head&lt;/h2&gt;
&lt;p&gt;Entertaining booklet on the difference between your head and other people's
heads. Great companion to any meditation or spiritual practice.&lt;/p&gt;
&lt;h2 id="permutation-city"&gt;Permutation City&lt;/h2&gt;
&lt;p&gt;Reads like a "consciousness and computers are cool" story written by an
engineer. A few incredible ideas padded by weak storytelling and philosophical
exposition. Probably would've been better as a short story.&lt;/p&gt;
&lt;h2 id="accelerando"&gt;Accelerando&lt;/h2&gt;
&lt;p&gt;Probably the definitive sci-fi on post-human acceleration possibilities.&lt;/p&gt;
&lt;h2 id="timeless-way"&gt;The Timeless Way of Building&lt;/h2&gt;
&lt;p&gt;This book provides a wonderful fractal-like framework for thinking about design.
Its focus on objective/falsifiable "patterns" is surprisingly pragmatic. The
book is filled with delightful writing and great examples.&lt;/p&gt;
&lt;h2 id="moon-mistress"&gt;The Moon is a Harsh Mistres&lt;/h2&gt;
&lt;p&gt;Reads like "classic" sci-fi. Explores cool ideas on superintelligence and human
organization structures.&lt;/p&gt;
&lt;h2 id="writing-bones"&gt;Writing Down the Bones&lt;/h2&gt;
&lt;p&gt;Reflections on zen and writing. Very personable, but didn't feel applicable to
me.&lt;/p&gt;
&lt;h2 id="oaks"&gt;The Nature of Oaks&lt;/h2&gt;
&lt;p&gt;More than you ever wanted to know about oak trees. Excellent demonstration of
complex and fragile ecosystem interdependencies.&lt;/p&gt;
&lt;h2 id="breath"&gt;Breath&lt;/h2&gt;
&lt;p&gt;This book had lots of bold claims worth investigating.&lt;/p&gt;
&lt;h2 id="things-fall-apart-1"&gt;When Things Fall Apart&lt;/h2&gt;
&lt;p&gt;Her essays feel like an incredible mix of Alan Watts and Brené Brown. This stuff
is life-changing. I'm in love!&lt;/p&gt;
&lt;h2 id="walk-in-the-woods"&gt;A Walk in the Woods&lt;/h2&gt;
&lt;p&gt;An imperfect-yet-hilarious account of an ambitious hike up the Appalachian
Trail. Besides being charming and incredibly well-written, the ecological
tangents hit super hard. Species of all kinds are going extinct at unfathomable
rates. I've heard the stats before and was unmoved, but his anecdotes somehow
seared Nature's impending peril into my arteries.&lt;/p&gt;
&lt;h2 id="adult-children"&gt;Adult Children of Emotionally Immature Parents&lt;/h2&gt;
&lt;p&gt;This book should be required reading for anybody with strained family relations.
Although not very dense or academic, there are tons of gems to be found.&lt;/p&gt;
&lt;h2 id="see-the-sun"&gt;If You Could See The Sun&lt;/h2&gt;
&lt;p&gt;Interesting premise for young-adult-gets-superpower book, but with a cringey
execution.&lt;/p&gt;
&lt;h2 id="extra-virginity"&gt;Extra Virginity&lt;/h2&gt;
&lt;p&gt;And suddenly I'm an olive oil snob.&lt;/p&gt;
&lt;h2 id="player-piano"&gt;Player Piano&lt;/h2&gt;
&lt;p&gt;It's not Vonnegut's funniest nor best-written work, but it's a parable of
technology/automation that will have a lasting impact on me.&lt;/p&gt;
&lt;h2 id="rest-ag"&gt;Restoration Agriculture&lt;/h2&gt;
&lt;p&gt;One of the most practical takes on permaculture, but somehow integrates a
long-view of how biomes develop on Earth. Very convincing arguments for
switching to perennial-oriented farming. Strong and reasonable opinions that I
haven't heard via other permaculture channels.&lt;/p&gt;
&lt;h2 id="no-logo"&gt;No Logo&lt;/h2&gt;
&lt;p&gt;Thorough dissection of corporations, branding, and activism.&lt;/p&gt;
&lt;h2 id="shadow-lost"&gt;The Shadow of What Was Lost&lt;/h2&gt;
&lt;p&gt;Unoriginal young adult "chosen one" power fantasy with cool takes on time and
fate.&lt;/p&gt;
&lt;h2 id="giant-hairball"&gt;Orbiting the Giant Hairball&lt;/h2&gt;
&lt;p&gt;Whimsical take on corporate creativity. Seems useful for people trapped in
certain types of large organizations.&lt;/p&gt;
&lt;h2 id="publicly-shamed"&gt;So You've Been Publicly Shamed&lt;/h2&gt;
&lt;p&gt;A well-written book on a topic I care surprisingly little about.&lt;/p&gt;
&lt;h2 id="left-hand-darkness"&gt;The Left Hand of Darkness&lt;/h2&gt;
&lt;p&gt;Solid science fiction with awesome spiritual insight.&lt;/p&gt;
&lt;h2 id="omnivores-dilemma"&gt;The Omnivore's Dilemma&lt;/h2&gt;
&lt;p&gt;A rare non-fiction masterpiece. Pollan asks and answers all the right questions
about humanity's relationship with food.&lt;/p&gt;
&lt;h2 id="everyday-things"&gt;The Design of Everyday Things&lt;/h2&gt;
&lt;p&gt;The definitive guide to design -- a must-read for all makers and managers.&lt;/p&gt;
&lt;h2 id="inner-game"&gt;The Inner Game of Tennis&lt;/h2&gt;
&lt;p&gt;A strange but wonderful book that teaches how to teach/learn through
meditation-like techniques.&lt;/p&gt;
&lt;h2 id="triumph-of-seeds"&gt;Triumph of Seeds&lt;/h2&gt;
&lt;p&gt;Seed biology is a super cool topic, but the author's writing style irked me.&lt;/p&gt;
&lt;h2 id="bird-by-bird"&gt;Bird by Bird&lt;/h2&gt;
&lt;p&gt;Solid writing advice and potentially good memoir smashed into a mediocre
package.&lt;/p&gt;
&lt;h2 id="diamond-age"&gt;The Diamond Age&lt;/h2&gt;
&lt;p&gt;An incredible exploration of nanotech, education, global borders &amp;amp; ethnic
groups, and intuition. Some character arcs were a tad disappointing, but that
can be forgiven in the sheer volume of ideas put to paper.&lt;/p&gt;
&lt;h2 id="zen-writing"&gt;Zen in the Art of Writing&lt;/h2&gt;
&lt;p&gt;A "zesty" book that is both a disappointing memoir and unhelpful writing guide.&lt;/p&gt;
&lt;h2 id="read-nonfiction"&gt;How to Read Nonfiction Like a Professor&lt;/h2&gt;
&lt;p&gt;Insufferable drivel.&lt;/p&gt;
&lt;h2 id="first-law"&gt;The First Law Trilogy&lt;/h2&gt;
&lt;p&gt;An unremarkable young-adult fantasy series with a bold ending that somehow
captured my attention.&lt;/p&gt;
&lt;h2 id="company-of-one"&gt;Company of One&lt;/h2&gt;
&lt;p&gt;I struggled to find anything interesting or actionable from this book. I'd
suggest reading Derek Sivers's &lt;em&gt;Anything You Want&lt;/em&gt; instead, which delivers many
of the same opinions in a potent dose.&lt;/p&gt;
&lt;h2 id="fair-play"&gt;Fair Play&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Fair Play&lt;/em&gt; is a manifesto and guide for balancing domestic labor. I think it
made a lot of good points, and I can imagine its clever card game/exercise being
a useful tool for couples. I'd recommend this book to couples straining to
fairly distribute work. I suspect that clear division of responsibilities with
moderate time-tracking would be a great move for all households.&lt;/p&gt;
&lt;h2 id="station-11"&gt;Station Eleven&lt;/h2&gt;
&lt;p&gt;Well-written book with a premise that didn't hook me.&lt;/p&gt;
&lt;h2 id="real-life"&gt;We Are Never Meeting in Real Life&lt;/h2&gt;
&lt;p&gt;Funny/sad essays about health and money and maturity and love.&lt;/p&gt;
&lt;h2 id="your-life"&gt;Story of Your Life and Others&lt;/h2&gt;
&lt;p&gt;Another incredible collection of sci-fi shorts by Ted Chiang.&lt;/p&gt;
&lt;h2 id="kafka-shore"&gt;Kafka on the Shore&lt;/h2&gt;
&lt;p&gt;I totally get that this book is subtle and clever, but sorry, it didn't really
make feel anything. And the sheer volume of media references made me feel like I
was reading &lt;em&gt;Ready Player One&lt;/em&gt; for self-aggrandized Mensa members.&lt;/p&gt;
&lt;p&gt;The 19-year-old-philosophy-student-sex-scene from &lt;em&gt;Kafka on the Shore&lt;/em&gt; embodies
much of Murakami: pretentious, captivating, and horny.&lt;/p&gt;
&lt;h2 id="ove"&gt;A Man Called Ove&lt;/h2&gt;
&lt;p&gt;Heart-warming dark comedy about a grumpy old man who wants to die.&lt;/p&gt;
&lt;h2 id="paper-menagerie"&gt;Paper Menagerie&lt;/h2&gt;
&lt;p&gt;Great short story collection exploring China, technology, and the power of
words.&lt;/p&gt;
&lt;h2 id="white-noise"&gt;White Noise&lt;/h2&gt;
&lt;p&gt;This book produces a cool metanarrative about finding meaning amongst noise. It
also made me chuckle in quite a few parts. I really liked the characterization
of the main couple; despite its humorous takes, I think the story displays a lot
of elements of a realistic healthy relationship. I only read one third of this
book, because I'm a bit burnt out on modern/postmodern literature right now. It
seems worth another try at some point in the future though.&lt;/p&gt;
&lt;h2 id="grapes-of-wrath"&gt;The Grapes of Wrath&lt;/h2&gt;
&lt;p&gt;This book is a passionate exploration of United States' culture. It brilliantly
covers capitalism, hunger, determination, distance, inequality, crime, family,
and xenophobia. I also loved the poetic flourishes littered throughout the
story. The world would probably be a kinder place if everybody read this book.&lt;/p&gt;
&lt;h2 id="swim-pond-rain"&gt;A Swim in a Pond in the Rain&lt;/h2&gt;
&lt;p&gt;A collection of engaging short stories with helpful analyses and writing advice.&lt;/p&gt;
&lt;p&gt;The audiobook is read by an incredible cast:
&lt;a href="https://www.penguinrandomhouseaudio.com/narrator/26961/george-saunders/"&gt;George Saunders&lt;/a&gt;,
&lt;a href="https://www.penguinrandomhouseaudio.com/narrator/57827/phylicia-rashad/"&gt;Phylicia Rashad&lt;/a&gt;,
&lt;a href="https://www.penguinrandomhouseaudio.com/narrator/241710/nick-offerman/"&gt;Nick Offerman&lt;/a&gt;,
&lt;a href="https://www.penguinrandomhouseaudio.com/narrator/43266/glenn-close/"&gt;Glenn Close&lt;/a&gt;,
&lt;a href="https://www.penguinrandomhouseaudio.com/narrator/2254326/keith-david/"&gt;Keith David&lt;/a&gt;,
&lt;a href="https://www.penguinrandomhouseaudio.com/narrator/223820/rainn-wilson/"&gt;Rainn Wilson&lt;/a&gt;,
&lt;a href="https://www.penguinrandomhouseaudio.com/narrator/230003/bd-wong/"&gt;BD Wong&lt;/a&gt;, and
&lt;a href="https://www.penguinrandomhouseaudio.com/narrator/2254327/rene-elise-goldsberry/"&gt;Renée Elise Goldsberry&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="black-prism"&gt;The Black Prism&lt;/h2&gt;
&lt;p&gt;Decent fantasy story with a compelling hard magic system based on the light
spectrum.&lt;/p&gt;
&lt;h2 id="lucifers-hammer"&gt;Lucifer's Hammer&lt;/h2&gt;
&lt;p&gt;An extraordinary number of people hope for the world to end in their lifetimes.&lt;/p&gt;
&lt;h2 id="time-war"&gt;This Is How You Lose the Time War&lt;/h2&gt;
&lt;p&gt;Borderline pretentious but short, beautiful, and entertaining.&lt;/p&gt;
&lt;h2 id="rosie"&gt;The Rosie Project&lt;/h2&gt;
&lt;p&gt;Delightful romance story from the perspective of an autistic man.&lt;/p&gt;
&lt;h2 id="100-year-old-man"&gt;The 100-Year-Old Man Who Climbed Out the Window and Disappeared&lt;/h2&gt;
&lt;p&gt;An irreverent Forrest Gump story. Incredible comedic payoffs. Required reading
for rascals.&lt;/p&gt;
&lt;h2 id="breakfast-of-champions"&gt;Breakfast of Champions&lt;/h2&gt;
&lt;p&gt;A wacky book about crazy people for crazy people by crazy people. It's
Vonnegut's incisive alien wit at its best.&lt;/p&gt;
&lt;h2 id="hitchhikers"&gt;The Hitchhiker's Guide to the Galaxy&lt;/h2&gt;
&lt;p&gt;Classic, clever comedy.&lt;/p&gt;
&lt;h2 id="baby-led-feeding"&gt;Baby-Led Feeding&lt;/h2&gt;
&lt;p&gt;This book details how and why to transition babies to solids early. It provides
helpful guidance on safety and nutrition. The book comprises mostly simple
recipes that seem tasty enough for adults and babies to share.&lt;/p&gt;
&lt;h2 id="no-lies"&gt;Tell Me No Lies&lt;/h2&gt;
&lt;p&gt;Wading through pages of filler to find a few worthwhile gems about lies in
relationships. Sam Harris's short treatise on lying is a more potent and
practical read.&lt;/p&gt;
&lt;h2 id="atlas-of-the-heart"&gt;Atlas of the Heart&lt;/h2&gt;
&lt;p&gt;An engaging encyclopedia of emotions.&lt;/p&gt;
&lt;h2 id="exhalation"&gt;Exhalation: Stories&lt;/h2&gt;
&lt;p&gt;A provocative short story collection about humans and technology. Each story
will leave a lasting impact on how I see the world. Highly, highly recommended.&lt;/p&gt;
&lt;h2 id="one-straw"&gt;The One-Straw Revolution&lt;/h2&gt;
&lt;p&gt;Human hubris knows no bounds. This book spurns modernity. It's a charming case
against science, technology, and progress. The pursuit of comfort makes life
untenable.&lt;/p&gt;
&lt;h2 id="how-we-got-to-now"&gt;How We Got To Now&lt;/h2&gt;
&lt;p&gt;This book is the best kinds of entertaining and informative. Six threads of
technological history demonstrate the interconnectedness of ideas. It's a
refreshing take on inevitable innovation.&lt;/p&gt;
&lt;h2 id="walking-man"&gt;The Walking Man&lt;/h2&gt;
&lt;p&gt;Short manga stories about the experience of living. Breathtaking artwork in some
parts. Inspired me to get drenched and walk barefoot more often.&lt;/p&gt;
&lt;h2 id="wintringham-mystery"&gt;The Wintringham Mystery&lt;/h2&gt;
&lt;p&gt;Clever mystery, but ultimately soulless.&lt;/p&gt;
&lt;h2 id="tomorrow"&gt;Tomorrow, and Tomorrow, and Tomorrow&lt;/h2&gt;
&lt;p&gt;Deep and emotional and insightful. A love letter to love in the language of game
design. This book oozes "millennial", and that's a surprisingly good thing. It
feels like classic literature written yesterday.&lt;/p&gt;
&lt;h2 id="a-confederacy-of-dunces"&gt;A Confederacy of Dunces&lt;/h2&gt;
&lt;p&gt;Nutty Professor meets Don Quixote meets Infinite Jest. A literary landmark ahead
of its time.&lt;/p&gt;
&lt;h2 id="bridget-joness-diary"&gt;Bridget Jones's Diary&lt;/h2&gt;
&lt;p&gt;An endearing read about the difficulties of young adult life and womanhood in
the modern era. Contains great tongue-in-cheek commentaries on health, feminism,
egotism, and addiction.&lt;/p&gt;
&lt;h2 id="promise-of-blood"&gt;Promise of Blood&lt;/h2&gt;
&lt;p&gt;It's a charming adventure/fantasy story with good characters and cool magic --
nothing revolutionary.&lt;/p&gt;
&lt;h2 id="anything-you-want"&gt;Anything You Want&lt;/h2&gt;
&lt;p&gt;I adore this short book. &lt;em&gt;Anything You Want&lt;/em&gt; celebrates simplicity and
sincerity. It reads like a guide to entrepeneurship for Taoists. I've read a lot
of business books, and none come close to hitting this hard. Companies forget
their customers and drink bureaucratic kool-aid in the pursuit of profits. As
I'm starting my own business, I keep coming back to this book to remind me of
&lt;em&gt;why&lt;/em&gt; I'm doing what I'm doing.&lt;/p&gt;
&lt;h2 id="learned-optimism"&gt;Learned Optimism&lt;/h2&gt;
&lt;p&gt;This book has exactly three good observations: (1) optimists attribute permanent
causes to good events and temporary causes to bad events, (2) optimism is useful
when risk is minimal, and (3) optimism is dangerous in risky situations.
&lt;em&gt;Learned Optimism&lt;/em&gt; would have been an excellent 250-word book.&lt;/p&gt;
&lt;h2 id="to-say-nothing-of-the-dog"&gt;To Say Nothing of the Dog&lt;/h2&gt;
&lt;p&gt;Witty, but distractingly British.&lt;/p&gt;
&lt;h2 id="get-in-trouble"&gt;Get In Trouble&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;The Summer People&lt;/em&gt; is one of the best short stories I've ever encountered. The
other stories in this collection are well-written but failed to hit me in the
feels -- intriguing plot premises and a great literary style wasted on
young-adult subject matter.&lt;/p&gt;
&lt;h2 id="slade-house"&gt;Slade House&lt;/h2&gt;
&lt;p&gt;This book is a structural marvel but hollow inside. It gives great pacing with
lackluster payoffs.&lt;/p&gt;
&lt;h2 id="the-bell-jar"&gt;The Bell Jar&lt;/h2&gt;
&lt;p&gt;This is a must-read for anybody who wants to learn what it's like to have a
mental breakdown, or love somebody in a dark place. This book was well-written
but too painfully familiar. This book made me sad without being charming or
teaching me anything new. It stole from me and gave me nothing in return, but I
imagine it has much to give others.&lt;/p&gt;
&lt;h2 id="the-fall-of-hyperion"&gt;The Fall of Hyperion&lt;/h2&gt;
&lt;p&gt;This book seemed to have a lot to say. It wanted to be philosophical, it wanted
to be adventurous, it wanted to be suspenseful, it wanted to be mysterious, and
it wanted to be thought-provoking. But in my opinion, it didn't do a great job
at anything in particular. Or maybe its brilliance was wasted on me.&lt;/p&gt;
&lt;h2 id="the-anthropocene-reviewed"&gt;The Anthropocene Reviewed&lt;/h2&gt;
&lt;p&gt;Painfully human essays; five stars.&lt;/p&gt;
&lt;h2 id="me-talk-pretty-one-day"&gt;Me Talk Pretty One Day&lt;/h2&gt;
&lt;p&gt;Witty and deeply personal essay collection. Great insights on parenting, grief,
pets, language, and addiction.&lt;/p&gt;
&lt;h2 id="zero-to-one"&gt;Zero to One&lt;/h2&gt;
&lt;p&gt;Solid book on the how &amp;amp; why of founding a startup.&lt;/p&gt;
&lt;h2 id="artemis"&gt;Artemis&lt;/h2&gt;
&lt;p&gt;This book is a well-researched sci-fi MacGyver adventure story. It's got great
characters and a fast plot. Solid book.&lt;/p&gt;
&lt;h2 id="hyperion"&gt;Hyperion&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Hyperion&lt;/em&gt; is unusually refreshing sci-fi. It's 6-8 interconnected short stories
with drama, horror, mystery, and awe. Some sections didn't age well, but the
writing is incredible in some parts, and some of its ideas are downright
brilliant.&lt;/p&gt;
&lt;h2 id="the-war-of-art"&gt;The War of Art&lt;/h2&gt;
&lt;p&gt;If you need inspiration or a swift kick-in-the-butt, read this book. Since
reading, I've been unable to make my usual excuses that divert me from my art
and career and health. For me, every page contained a mixture of timeless wisdom
and awful advice. This book is overall charming and pragmatic.&lt;/p&gt;
&lt;h2 id="gods-debris"&gt;God's Debris&lt;/h2&gt;
&lt;p&gt;From few axioms, &lt;em&gt;God's Debris&lt;/em&gt; forms a surprisingly coherent religion,
philosophy of science, and practical life advice; a worthwhile ~90-minute read.&lt;/p&gt;
&lt;h2 id="interview-with-the-vampire"&gt;Interview with the Vampire&lt;/h2&gt;
&lt;p&gt;Imagine &lt;em&gt;The Count of Monte Cristo&lt;/em&gt; meets &lt;em&gt;Faust&lt;/em&gt; meets &lt;em&gt;Twilight&lt;/em&gt; -- it's an
epic tale with deep philosophical underpinnings… and vampires! This book is
worth reading if you want to dramatically ponder death, love, and evil.&lt;/p&gt;
&lt;h2 id="norwegian-wood"&gt;Norwegian Wood&lt;/h2&gt;
&lt;p&gt;This book should be labeled with an extreme trigger warning. It covers death and
sex and more death and more sex and a whole lot of depression. Imagine blending
together &lt;em&gt;Catcher in the Rye&lt;/em&gt;, &lt;em&gt;The Great Gatsby&lt;/em&gt;, &lt;em&gt;The Perks of Being a
Wallflower&lt;/em&gt;, &lt;em&gt;Scott Pilgrim&lt;/em&gt;, and a smut novel. I think I learned a lot about
selfishness and happiness from this experience. I wish I read this when I
was 19.&lt;/p&gt;
&lt;h2 id="the-way-of-kings"&gt;The Way of Kings&lt;/h2&gt;
&lt;p&gt;If Sanderson's &lt;em&gt;Mistborn&lt;/em&gt; is &lt;em&gt;The Chronicles of Narnia&lt;/em&gt;, then &lt;em&gt;The Stormlight
Archives&lt;/em&gt; is &lt;em&gt;The Lord of the Rings&lt;/em&gt;. It's magical, political, and finely
crafted. Brandon Sanderson knows how to write payoffs. This book alone contains
383,000 words, so clear your schedule before reading.&lt;/p&gt;
&lt;h2 id="the-handmaids-tale"&gt;The Handmaid's Tale&lt;/h2&gt;
&lt;p&gt;Much of &lt;em&gt;The Handmaid's Tale&lt;/em&gt; is surely beyond my comprehension, but it provided
a sobering glimpse into some of the unfairnesses of nature and society. Atwood's
general take on United States fundamentalism is outstanding. The book is
riveting throughout, but its ending secures it a "modern masterpiece".&lt;/p&gt;
&lt;h2 id="animal-farm"&gt;Animal Farm&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Animal Farm&lt;/em&gt; delightfully illustrates how authoritarian regimes usurp control.
Although the book seemed to be specifically a critique of Stalin's Russia, there
are gems to glean about perverse institutional incentives.&lt;/p&gt;
&lt;h2 id="the-colour-of-magic"&gt;The Colour of Magic (Discworld #1)&lt;/h2&gt;
&lt;p&gt;This was my first Terry Pratchett book. Oh boy, what a delight! If you like
clever authors like Raymond Smullyan and Douglas Adams, you'll adore his
writing. &lt;em&gt;The Colour of Magic&lt;/em&gt; covers some surprisingly deep ideas about science
and religion wearing a witty grin.&lt;/p&gt;
&lt;h2 id="the-remains-of-the-day"&gt;The Remains of the Day&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;The Remains of the Day&lt;/em&gt; is a subtle story from the perspective of a charming
butler. I highly recommend this book if you're in a wistful mood or receptive to
deep introspection.&lt;/p&gt;
&lt;h2 id="the-stranger"&gt;The Stranger&lt;/h2&gt;
&lt;p&gt;This book is very highly acclaimed. After reading it, I thought I missed
something, so I read a few critical analyses -- nope, I understood it perfectly.
&lt;em&gt;The Stranger&lt;/em&gt; has a decent plot with great writing. This book was probably
ground-breaking in 1942, but existentialism has been beaten to death since then.
On my end, this is probably a case of
&lt;a href="https://tvtropes.org/pmwiki/pmwiki.php/Main/SeinfeldIsUnfunny"&gt;"Seinfeld is unfunny"&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="metamorphosis"&gt;Metamorphosis&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Metamorphosis&lt;/em&gt; is potent. It's witty, horrific, deep, and bizarre -- an
unforgettable experience.&lt;/p&gt;
&lt;h2 id="discipline-is-destiny"&gt;Discipline is Destiny&lt;/h2&gt;
&lt;p&gt;This book was inspiring but forgettable. All the examples of disciplined people
got me &lt;em&gt;pumped&lt;/em&gt;, but I didn't find much actionable content.&lt;/p&gt;
&lt;h2 id="ubik"&gt;Ubik&lt;/h2&gt;
&lt;p&gt;Good sci-fi stories explore "cool ideas". &lt;em&gt;Ubik&lt;/em&gt; miraculously interleaves 3-5
"cool ideas". And it keeps topping itself with bigger and bigger extensions of
its core "cool ideas". Read &lt;em&gt;Ubik&lt;/em&gt; if you like "cool ideas".&lt;/p&gt;
&lt;h2 id="triggers"&gt;Triggers&lt;/h2&gt;
&lt;p&gt;This is a self-help book that delivers. It's an incredibly useful guide on how
to enact lasting change in yourself. This book focuses on growth via
environmental cues. I particularly liked the concrete advice on daily active
questions, accountability partners, and actionable ways to change cognitive
contexts.&lt;/p&gt;
&lt;h2 id="book-of-the-new-sun"&gt;Book of the New Sun&lt;/h2&gt;
&lt;p&gt;These books are extremely subversive, yet the author falls prey to the exact
tropes he's "subverting". The plot oscillates between mind-bending
self-reference and painful self-awarelessness. Some of the twists are
incredible. I suspect that this is an excellent book that aged poorly.&lt;/p&gt;
&lt;h2 id="solaris"&gt;Solaris&lt;/h2&gt;
&lt;p&gt;A sci-fi book that gave me literal nightmares. It's unsettling and
thought-provoking.&lt;/p&gt;
&lt;h2 id="the-good-earth"&gt;The Good Earth&lt;/h2&gt;
&lt;p&gt;A heartbreaking rags-to-riches-to-rags story about wealth, family, work,
fairness, kindness, and &lt;em&gt;land&lt;/em&gt;. It's easy to see why a book about rural Chinese
peasants was the best-selling book in the US during The Dust Bowl and The Great
Depression. This book will inspire or haunt you.&lt;/p&gt;</description><author>taylor.town</author><pubDate>Sun, 26 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/books</guid></item><item><title>Default Apps in 2023</title><link>https://huphtur.nl/2023-default-apps/</link><description>&lt;p&gt;Found via a &lt;a href="https://front-end.social/@chriscoyier/111474487605531383"&gt;toot&lt;/a&gt; by &lt;a href="https://chriscoyier.net/2023/11/25/default-apps-2023/"&gt;Chris Hoyer&lt;/a&gt;, this list of &lt;a href="https://defaults.rknight.me/"&gt;default apps&lt;/a&gt;. Started by the folks from the &lt;a href="https://listen.hemisphericviews.com/"&gt;Hemispheric Views&lt;/a&gt; podcast in an episode dubbed &lt;em&gt;&lt;a href="https://listen.hemisphericviews.com/097"&gt;Duel of the Defaults!&lt;/a&gt;&lt;/em&gt; to find out which person uses the most default apps. After listening to the episode &lt;a href="https://www.strava.com/activities/10286773646"&gt;while Zwifting&lt;/a&gt; I decided to jot down my own list.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;📨 Mail Client: Mail&lt;/li&gt;
&lt;li&gt;📮 Mail Server: iCloud&lt;/li&gt;
&lt;li&gt;📝 Notes: Notes&lt;/li&gt;
&lt;li&gt;✅ To-Do: Reminders&lt;/li&gt;
&lt;li&gt;📷 iPhone Photo Shooting: Camera&lt;/li&gt;
&lt;li&gt;🟦 Photo Management: Photos&lt;/li&gt;
&lt;li&gt;📆 Calendar: Calendar&lt;/li&gt;
&lt;li&gt;📁 Cloud File Storage: iCloud Drive&lt;/li&gt;
&lt;li&gt;📖 RSS: &lt;a href="https://www.inoreader.com/"&gt;Innoreader&lt;/a&gt;. Very similar to Feedly, which I ditched after they started &lt;a href="https://www.pcmag.com/news/feedly-faces-backlash-over-protest-tracking-ai-models"&gt;using AI&lt;/a&gt; for all the wrong reasons.&lt;/li&gt;
&lt;li&gt;🙍🏻‍♂️ Contacts: Contacts&lt;/li&gt;
&lt;li&gt;🌐 Browser: &lt;a href="https://www.mozilla.org/en-US/firefox/new/"&gt;Firefox&lt;/a&gt; because of &lt;a href="https://ublockorigin.com/"&gt;uBlock&lt;/a&gt; ad blocker.&lt;/li&gt;
&lt;li&gt;💬 Chat: &lt;a href="https://discord.com/"&gt;Discord&lt;/a&gt;. Have encountered some great folks on the &lt;em&gt;members only&lt;/em&gt; &lt;a href="https://escapecollective.com/"&gt;Escape Collective&lt;/a&gt; Discord. If you’re into cycling, please join us!&lt;/li&gt;
&lt;li&gt;🔖 Bookmarks: Firefox because it has bookmark tags. If only Safari had better bookmarking support…&lt;/li&gt;
&lt;li&gt;📑 Read It Later: &lt;a href="https://getpocket.com/"&gt;Pocket&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;📜 Word Processing: Pages&lt;/li&gt;
&lt;li&gt;📈 Spreadsheets: Numbers&lt;/li&gt;
&lt;li&gt;📊 Presentations: Keynote&lt;/li&gt;
&lt;li&gt;🛒 Shopping Lists: &lt;a href="https://www.paprikaapp.com/"&gt;Paprika&lt;/a&gt;. Our favorite app to store all our recipes. The bookmarklet to import recipes is fantastic. The option to scale recipes to any portion is life saving (specially when dealing with silly imperial system). All the ingredients for a recipe can be added to a shopping list&lt;/li&gt;
&lt;li&gt;🍴 Meal Planning: Paprika.&lt;/li&gt;
&lt;li&gt;💰 Budgeting and Personal Finance: Wealthfront for both &lt;a href="https://www.wealthfront.com/invited/AFFC-866V-OG88-2XHX"&gt;Cash &amp;amp; Savings&lt;/a&gt; and &lt;a href="https://www.wealthfront.com/invited/AFFD-0AGC-6XLY-DKNX"&gt;Automated Investing&lt;/a&gt;. Set it and forget it.&lt;/li&gt;
&lt;li&gt;📰 News: RSS + a bookmark folder in Firefox filled with Dutch and English news sources. Would love to use News but the &lt;a href="https://mastodon.social/@gruber/111196532299204839"&gt;horrendous ads&lt;/a&gt; make it impossible to use.&lt;/li&gt;
&lt;li&gt;🎵 Music: Music&lt;/li&gt;
&lt;li&gt;🎤 Podcasts: &lt;a href="https://overcast.fm/"&gt;Overcast&lt;/a&gt;, but may switch to the Podcasts app. In fact, I listened to the &lt;em&gt;Duel of the Defaults!&lt;/em&gt; episode in the Podcasts app.&lt;/li&gt;
&lt;li&gt;🔐 Password Management: 1Password, but have been in the process of migrating to Passwords this year already.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So if we go by Hemispheric Views’ extremely scientific scoring system I would end up with ~36 points. Not even close to the winner, &lt;a href="https://loungeruminator.net/2023/11/17/mr-default/"&gt;Martin Feld&lt;/a&gt;, who ended up with 33 points.&lt;/p&gt;
&lt;p&gt;For 2024 I’m going to be training hard (by switching from Firefox to Safari), which should put me in the lead with 30 points. I’m coming for that &lt;em&gt;Mr. Default&lt;/em&gt; crown Martin!&lt;/p&gt;</description><author>huphtur</author><pubDate>Sun, 26 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://huphtur.nl/2023-default-apps/</guid></item><item><title>Roc</title><link>https://mfashby.net/posts/2023-11-25-roc/</link><description/><author>Home on mfashby.net</author><pubDate>Sat, 25 Nov 2023 23:19:27 GMT</pubDate><guid isPermaLink="true">https://mfashby.net/posts/2023-11-25-roc/</guid></item><item><title>2023 Holiday gift guide</title><link>https://somethingaboutcats.substack.com/p/2023-holiday-gift-guide</link><description>A compendium of stuff I love that you too can own</description><author>Something About Cats</author><pubDate>Sat, 25 Nov 2023 19:22:21 GMT</pubDate><guid isPermaLink="true">https://somethingaboutcats.substack.com/p/2023-holiday-gift-guide</guid></item><item><title>After Rustlab</title><link>https://dorotac.eu/posts/rustlab/</link><description>&lt;h1&gt;After RustLab&lt;/h1&gt;
&lt;p&gt;Last week I attended the &lt;a href="https://rustlab.it/"&gt;RustLab&lt;/a&gt; conference for the first time. This report was going to be rather boring, but one of the presentations triggered me, so I'm going to put the (hopefully interesting) hot take right here in the front.&lt;/p&gt;
&lt;h2&gt;Last-minute talk&lt;/h2&gt;
&lt;p&gt;The talk about "confidential computing" was not on the schedule. It took the slot of a cancelled one, after some confusion regarding lightning talks in the same spot. Please excuse me that I don't know the exact title or the author (I will update the post once I find the recording).&lt;/p&gt;
&lt;h2&gt;Same, but different&lt;/h2&gt;
&lt;p&gt;The speaker asked the audience for a show of hands: who heard the term "confidential computing"? Very few hands went up.&lt;/p&gt;
&lt;p&gt;Then he explained that "confidential computing" prevents anyone from accessing confidential data, and even the staff at the data center cannot access your computations. This is due to hardware features like TrustZone, hypervisors, Intel SGX, and similar.&lt;/p&gt;
&lt;p&gt;Sounds familiar?&lt;/p&gt;
&lt;p&gt;How many people would have put their hands up if he asked about "trusted computing"? Or "DRM"? Was the selection of the term intentional? I can only wonder.&lt;/p&gt;
&lt;p&gt;Trusted computing can be used to empower. At &lt;a href="https://puri.sm"&gt;Purism&lt;/a&gt;, my co-workers used &lt;a href="https://osresearch.net/"&gt;trusted computing&lt;/a&gt; to give the user control over who can tamper with their OS. My own team added a smart card reader to the &lt;a href="https://puri.sm/products/librem-5/"&gt;Librem 5&lt;/a&gt; for similar purposes. But this is an exception, rather than a rule. Hardware manufacturers use trusted computing technologies to &lt;a href="https://www.xda-developers.com/huawei-honor-request-bootloader-unlock-code/"&gt;prevent&lt;/a&gt; &lt;a href="https://xdaforums.com/t/unlock-bootloader-yes-but-unlocking-seems-impossible.3788708/"&gt;people&lt;/a&gt; from loading their software of choice on the devices they own. Some don't give up, and eventually &lt;a href="https://switch.homebrew.guide/"&gt;break into&lt;/a&gt; their own devices to gain full(-er) access.&lt;/p&gt;
&lt;p&gt;The speaker completely ignored that topic, instead describing the rather tame use case where we pay someone else to do our computations – the classic cloud computing arrangement. Trusted computing then ensures that our contractor doesn't see or mess with our inputs or outputs. That's a quite reasonable take on things.&lt;/p&gt;
&lt;p&gt;Except it's like presenting the scientific benefits of the ballistic rocket and staying silent on its applications in war.&lt;/p&gt;
&lt;h2&gt;Root of trust&lt;/h2&gt;
&lt;p&gt;For a talk about controlling access to data, there was disappointingly little said about who's wielding that control. Even when someone from the audience (&lt;em&gt;moi&lt;/em&gt;) asked about it, the answer didn't really include the core concept of processing secrets: the &lt;em&gt;root of trust&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;In very simple terms, the root of trust is the part of the system that the data cannot be hidden from.&lt;/p&gt;
&lt;p&gt;As the presenter said, CPU doing the actual processing is one. Translating to more useful terms: you must trust the CPU maker to create a CPU free of bugs and not to intentionally exfiltrate your data. This is the CPU has direct access to your data, and verifying that the CPU is OK after buying it is &lt;a href="https://security.stackexchange.com/questions/241303/how-can-you-trust-that-there-is-no-backdoor-in-your-hardware"&gt;damn near impossible&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But there's an even more important party here. It's the one who actually wants to process the data. The one who loaded the application. If the application could not access data it wants to confidentially process, it wouldn't be useful for much. So the application loader also holds the keys to the kingdom. Quite literally, because the CPU interface is designed to grant access to the confidential computing facilities to anyone who holds one of the cryptographic keys burned (in)directly into the CPU.&lt;/p&gt;
&lt;p&gt;If you load the application, but haven't thoroughly verified its source code, then the authors of the source also become roots of trust – but that's a topic for another day.&lt;/p&gt;
&lt;h2&gt;It's mine! No, it's mine!&lt;/h2&gt;
&lt;p&gt;So, who's the key holder in practice?&lt;/p&gt;
&lt;p&gt;For Secure Boot on x86, Microsoft is one by default. Linux distributions like Fedora need to beg Microsoft to &lt;a href="https://jfearn.fedorapeople.org/fdocs/en-US/Fedora_Draft_Documentation/0.1/html/UEFI_Secure_Boot_Guide/sect-UEFI_Secure_Boot_Guide-Implementation_of_UEFI_Secure_Boot-Shim.html"&gt;give them access&lt;/a&gt;, or otherwise installation would stop with scary warnings by default.&lt;/p&gt;
&lt;p&gt;For Android boot, the phone manufacturer typically holds the keys. The firmware running in &lt;a href="https://genode.org/documentation/articles/trustzone"&gt;TrustZone&lt;/a&gt; typically cannot be replaced by anyone else.&lt;/p&gt;
&lt;p&gt;But if you're the user, the situation is not hopeless. Many CPU manufacturers on the ARM side keep TrustZone accessible. On the Librem 5, we deliberately left all 4 key slots open, for situations where the owner wants to create their own trusted computing environment.&lt;/p&gt;
&lt;p&gt;Some people say that not allowing the owner to access the keys is a good thing. After all, the keys protect the owner's confidential data from attackers, and if the users got the freedom to protect themselves, they would certainly hurt themselves, therefore they should not have the option. I will mercifully not comment on this kind of argument.&lt;/p&gt;
&lt;p&gt;Instead, I will encourage you, dear hardware and software maker, to stop for a moment, and think. Are you using confidential computing to empower people who own their hardware, or to split them from the computing power they paid for and own? Are you letting them make decisions, or making it for them?&lt;/p&gt;
&lt;p&gt;This talk forgot to place humans in context of confidential computing, even though all computing is one for humans. Please don't make that mistake and don't forget who your work serves.&lt;/p&gt;</description><author>dcz's posts</author><pubDate>Sat, 25 Nov 2023 16:00:00 GMT</pubDate><guid isPermaLink="true">https://dorotac.eu/posts/rustlab/</guid></item><item><title>Favourite books</title><link>https://ilearnt.com/blog/favouritebooks/</link><description>&lt;p&gt;The top shelf of books in my study has the books that have had the most impact on me or that I have enjoyed reading the most.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Sat, 25 Nov 2023 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/favouritebooks/</guid></item><item><title>[Hμblog] Towards Greater Accountability: A Proposal for CA Issuance Decision Logs</title><link>https://www.devever.net/~hl/u/202311#25a</link><description>&lt;ul&gt;&lt;li&gt;&lt;a href="https://unmitigatedrisk.com/?p=731"&gt;Towards Greater Accountability: A Proposal for CA Issuance Decision Logs&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://unmitigatedrisk.com/?p=729"&gt;Exploring the Potential of Domain Control Notaries for MPDV in WebPKI&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Interesting articles by &lt;a href="https://unmitigatedrisk.com/"&gt;Ryan Hurst&lt;/a&gt; about
possible improvements to WebPKI security via more certificate transparency
(CT)-like technologies. There's also &lt;a href="https://www.devever.net/~hl/expect-ct-lite"&gt;my own writings on
CT&lt;/a&gt;.&lt;/p&gt;</description><author>devever.net/~hl</author><pubDate>Sat, 25 Nov 2023 13:04:45 GMT</pubDate><guid isPermaLink="true">https://www.devever.net/~hl/u/202311#25a</guid></item><item><title>A practical introduction to kill-safe, concurrent programming in Racket</title><link>https://nikhilism.com/post/2023/racket-concurrency/</link><description>I&amp;rsquo;ve written a long tutorial exploring the user of Racket&amp;rsquo;s Concurrent ML (CML) inspired concurrency paradigm to write an API wrapping git-cat-file. It is meant to serve as an introduction to kill-safety and CML concurrency, while assuming some existing knowledge of Racket or other Schemes. I found that there weren&amp;rsquo;t a lot of resources beyond the reference documentation that explained how to put these APIs together, and I hope this can fill that hole.</description><author>Nikhil's blog</author><pubDate>Sat, 25 Nov 2023 07:35:08 GMT</pubDate><guid isPermaLink="true">https://nikhilism.com/post/2023/racket-concurrency/</guid></item><item><title>The limited utility of the phrase “GNU/Linux”</title><link>https://seirdy.one/posts/2022/12/09/limited-utility-gnu-linux/</link><description>“GNU/Linux” distinguishes Linux distributions primarily by the presence of GNU software, which is seldom the most meaningful or practical distinction.</description><author>All content on Seirdy’s Home</author><pubDate>Sat, 25 Nov 2023 07:00:04 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/posts/2022/12/09/limited-utility-gnu-linux/</guid></item><item><title>Native Emacs/macOS UX integrations via Swift modules</title><link>https://xenodium.com/native-emacsmacos-ux-integrations-via-swift-modules</link><description>&lt;p&gt;Once you learn a little &lt;a href="https://en.wikipedia.org/wiki/Emacs_Lisp"&gt;elisp&lt;/a&gt;, &lt;a href="https://www.gnu.org/software/emacs/"&gt;Emacs&lt;/a&gt; becomes this hyper malleable editor/platform. A live playground of sorts, where almost everything is up for grabs at runtime. Throw some elisp at it, and you can customize or extend almost anything to your heart's content. I say almost, as there's a comparatively small native core, that would typically require recompiling if you wanted to make further (native) mods. But that isn't entirely true. &lt;a href="https://www.masteringemacs.org/article/whats-new-in-emacs-25-1"&gt;Emacs 25&lt;/a&gt; enabled us to further extend things by loading native &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Dynamic-Modules.html"&gt;dynamic modules&lt;/a&gt;, back in 2016.&lt;/p&gt;
&lt;p&gt;Most of my Emacs-bending adventures have been powered by elisp, primarily on macOS. I also happen to have an iOS dev background, so when &lt;a href="https://github.com/SavchenkoValeriy"&gt;Valeriy Savchenko&lt;/a&gt; &lt;a href="https://www.reddit.com/r/emacs/comments/wemj1z/writing_emacs_dynamic_modules_in_swift/"&gt;announced&lt;/a&gt; his project bringing &lt;a href="https://github.com/SavchenkoValeriy/emacs-swift-module"&gt;Emacs dynamic modules powered by Swift&lt;/a&gt;, I added it to my never-ending list of things to try out.&lt;/p&gt;
&lt;p&gt;Fast-forward to a year later, and &lt;a href="https://github.com/roife"&gt;Roife&lt;/a&gt;'s &lt;a href="https://www.reddit.com/r/emacs/comments/17vrmrk/emt_emacs_macos_tokenizer_for_enhanced_cjk_word/"&gt;introduction&lt;/a&gt; to &lt;a href="https://github.com/roife/emt"&gt;emt&lt;/a&gt; finally gave me that much-needed nudge to give &lt;a href="https://github.com/SavchenkoValeriy/emacs-swift-module"&gt;emacs-swift-module&lt;/a&gt; a try. While I wish I had done it earlier, I also wish &lt;a href="https://github.com/SavchenkoValeriy/emacs-swift-module"&gt;emacs-swift-module&lt;/a&gt; had gotten more visibility. Native extensions written in Swift can open up some some neat integrations using native macOS UX/APIs.&lt;/p&gt;
&lt;p&gt;While I'm new to Savchenko's &lt;a href="https://github.com/SavchenkoValeriy/emacs-swift-module"&gt;emacs-swift-module&lt;/a&gt;, the project has &lt;a href="https://savchenkovaleriy.github.io/emacs-swift-module/documentation/emacsswiftmodule/"&gt;wonderful documentation&lt;/a&gt;. It quickly got me on my way to build an experimental dynamic module introducing a native context menu for sharing files from my beloved editor.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/native-emacsmacos-ux-integrations-via-swift-modules/emacs-share.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Most of the elisp/native bridging magic happens with fairly little Swift code:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;try env.defun(
  &amp;quot;macos-module--share&amp;quot;,
  with: &amp;quot;&amp;quot;&amp;quot;
    Share files in ARG1.

    ARG1 must be a vector (not a list) of file paths.
    &amp;quot;&amp;quot;&amp;quot;
) { (env: Environment, files: [String]) in
  let urls = files.map { URL(fileURLWithPath: $0) }

  let picker = NSSharingServicePicker(items: urls)
  guard let view = NSApp.mainWindow?.contentView else {
    return
  }

  let x = try env.funcall(&amp;quot;macos--emacs-point-x&amp;quot;) as Int
  let y = try env.funcall(&amp;quot;macos--emacs-point-y&amp;quot;) as Int

  let rect = NSRect(
    x: x + 15, y: Int(view.bounds.height) - y + 15, width: 1, height: 1
  )
  picker.show(relativeTo: rect, of: view, preferredEdge: .maxY)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This produced an elisp &lt;code&gt;macos-module--share&lt;/code&gt; function I could easily access from elisp like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(defun macos-share ()
  &amp;quot;Share file(s) with other macOS apps.

If visiting a buffer with associated file, share it.

While in `dired', any selected files, share those.  If region is
active, share files in region.  Otherwise share file at point.&amp;quot;
  (interactive)
  (macos-module--share (vconcat (macos--files-dwim))))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On a side note, &lt;code&gt;(macos--files-dwim)&lt;/code&gt; chooses files depending on context. That is, &lt;a href="https://xenodium.com/emacs-dwim-do-what-i-mean/"&gt;do what I mean (DWIM) style&lt;/a&gt;. If there's a file associated with current buffer, share it. When in &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Dired.html"&gt;dired&lt;/a&gt; (the directory editor, aka file manager), look at region, selected files, or default to file at point.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(defun macos--files-dwim ()
  &amp;quot;Return buffer file (if available) or marked/region files for a `dired' buffer.&amp;quot;
  (if (buffer-file-name)
      (list (buffer-file-name))
    (or
     (macos--dired-paths-in-region)
     (dired-get-marked-files))))

(defun macos--dired-paths-in-region ()
  &amp;quot;If `dired' buffer, return region files.  nil otherwise.&amp;quot;
  (when (and (equal major-mode 'dired-mode)
             (use-region-p))
    (let ((start (region-beginning))
          (end (region-end))
          (paths))
      (save-excursion
        (save-restriction
          (goto-char start)
          (while (&amp;lt; (point) end)
            ;; Skip non-file lines.
            (while (and (&amp;lt; (point) end) (dired-between-files))
              (forward-line 1))
            (when (dired-get-filename nil t)
              (setq paths (append paths (list (dired-get-filename nil t)))))
            (forward-line 1))))
      paths)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I got one more example of a native macOS integration I added. Being an even simpler one, and in hindsight, I prolly should have introduced it first. In any case, this one reveals &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Dired.html"&gt;dired&lt;/a&gt; files in macOS's Finder app (including the selection itself).&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/native-emacsmacos-ux-integrations-via-swift-modules/reveal.webp" /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;try env.defun(
  &amp;quot;macos-module--reveal-in-finder&amp;quot;,
  with: &amp;quot;&amp;quot;&amp;quot;
    Reveal (and select) files in ARG1 in macOS Finder.

    ARG1 mus be a vector (not a list) of file paths.
    &amp;quot;&amp;quot;&amp;quot;
) { (env: Environment, files: [String]) in
  NSWorkspace.shared.activateFileViewerSelecting(files.map { URL(fileURLWithPath: $0) })
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The corresponding elisp is nearly identical to its &lt;code&gt;macos-share&lt;/code&gt; sibling:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(defun macos-reveal-in-finder ()
  &amp;quot;Reveal file(s) in macOS Finder.

If visiting a buffer with associated file, reveal it.

While in `dired', any selected files, reveal those.  If region is
active, reveal files in region.  Otherwise reveal file at point.&amp;quot;
  (interactive)
  (macos-module--reveal-in-finder (vconcat (macos--files-dwim))))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;My Swift module experiment introduces two native macOS UX integrations, now available via &lt;code&gt;M-x macos-share&lt;/code&gt; and &lt;code&gt;M-x macos-reveal-in-finder&lt;/code&gt;. I've pushed all code to it's own &lt;a href="https://github.com/xenodium/EmacsMacOSModule"&gt;repo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I hope this post brings visibility to the wonderful &lt;a href="https://github.com/SavchenkoValeriy/emacs-swift-module"&gt;emacs-swift-module&lt;/a&gt; project and sparks new, native, and innovative integrations for those on macOS. Can't wait to see what others can do with it.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Enjoying this content? Find it useful? Consider ✨&lt;a href="https://github.com/sponsors/xenodium"&gt;sponsoring&lt;/a&gt;✨.&lt;/em&gt;&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Sat, 25 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/native-emacsmacos-ux-integrations-via-swift-modules</guid></item><item><title>Moon Phases • Art</title><link>https://muffinman.io/art/moon/</link><description>&lt;div class="container"&gt;&lt;div class="art-page__content"&gt;&lt;p&gt;It is incredibly hard to take a good photo of these. Colors have metallic reflection which I wasn’t able to capture, but it is very prominent in person. They are plotted using five different color markers. I framed them separately and plan to take a better photo once they are on the wall.&lt;/p&gt;
&lt;p&gt;Code is available on my &lt;a href="https://github.com/Stanko/moon"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;&lt;/div&gt;&lt;table class="text-sm art-page__info"&gt;&lt;tr&gt;&lt;td&gt;&lt;b&gt;Created: &lt;/b&gt;&lt;/td&gt;&lt;td&gt;November 2023&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;b&gt;Size: &lt;/b&gt;&lt;/td&gt;&lt;td&gt;14.5x14.5cm&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;b&gt;Paper: &lt;/b&gt;&lt;/td&gt;&lt;td&gt;Fabriano Black Black 300gsm&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;b&gt;Pens: &lt;/b&gt;&lt;/td&gt;&lt;td&gt;UniBall Signo and Sakura Jelly Rolls&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;div class="art-gallery photoswipe-gallery"&gt;&lt;div class="art-gallery__cover"&gt;&lt;a class="gallery__link outline" href="./00.jpg"&gt;&lt;img alt="" class="gallery__image" height="1800" src="./00.jpg" width="1800" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="art-gallery__items"&gt;&lt;a class="gallery__link outline" href="./01.jpg"&gt;&lt;img alt="" class="gallery__image" height="2000" src="./w_500-h_500--01.jpg" width="2000" /&gt;&lt;/a&gt;&lt;a class="gallery__link outline" href="./02.jpg"&gt;&lt;img alt="" class="gallery__image" height="2000" src="./w_500-h_500--02.jpg" width="2000" /&gt;&lt;/a&gt;&lt;a class="gallery__link outline" href="./03.jpg"&gt;&lt;img alt="" class="gallery__image" height="2000" src="./w_500-h_500--03.jpg" width="2000" /&gt;&lt;/a&gt;&lt;a class="gallery__link outline" href="./04.jpg"&gt;&lt;img alt="" class="gallery__image" height="2000" src="./w_500-h_500--04.jpg" width="2000" /&gt;&lt;/a&gt;&lt;a class="gallery__link outline" href="./05.jpg"&gt;&lt;img alt="" class="gallery__image" height="2000" src="./w_500-h_500--05.jpg" width="2000" /&gt;&lt;/a&gt;&lt;a class="gallery__link outline" href="./06.jpg"&gt;&lt;img alt="" class="gallery__image" height="1046" src="./w_500-h_500--06.jpg" width="5150" /&gt;&lt;/a&gt;&lt;a class="gallery__link outline" href="./plot_800x800.mp4"&gt;&lt;video class="gallery__video" src="./plot_800x800.mp4#t=0.001"&gt;&lt;/video&gt;&lt;svg class="gallery__play-icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;path d="M405.2,232.9L126.8,67.2c-3.4-2-6.9-3.2-10.9-3.2c-10.9,0-19.8,9-19.8,20H96v344h0.1c0,11,8.9,20,19.8,20 c4.1,0,7.5-1.4,11.2-3.4l278.1-165.5c6.6-5.5,10.8-13.8,10.8-23.1C416,246.7,411.8,238.5,405.2,232.9z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;</description><author>Muffin Man</author><pubDate>Sat, 25 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://muffinman.io/art/moon/</guid></item><item><title>husband, father, obligation</title><link>https://taylor.town/husband-father-obligation</link><description>For some folks, family is a debt to be paid. They dole out love-shaped deeds in hope of karmic justice. They oblige to stave off guilt.</description><author>taylor.town</author><pubDate>Sat, 25 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/husband-father-obligation</guid></item><item><title>Festive offers for books on Python, Linux, Regular Expressions and more</title><link>https://learnbyexample.github.io/programming-deals-2023/</link><description>&lt;p&gt;Hello!&lt;/p&gt;
&lt;p&gt;Here are some exciting deals for my programming ebooks as well as from other creators.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="my-ebooks"&gt;My ebooks&lt;a class="zola-anchor" href="#my-ebooks"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Offers valid till 30-Nov-2023:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/all-books/FestiveOffer"&gt;All 13 Books Bundle&lt;/a&gt; — $10 (normal price $32)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/python-bundle/FestiveOffer"&gt;Learn by example Python bundle&lt;/a&gt; — $4 (normal price $15)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/py_regex/FestiveOffer"&gt;Understanding Python re(gex)?&lt;/a&gt; — FREE (normal price $10)&lt;/li&gt;
&lt;/ul&gt;
&lt;p align="center"&gt;&lt;a href="https://learnbyexample.gumroad.com/l/all-books/FestiveOffer"&gt;&lt;img alt="All books bundle" src="/images/books/all_books_bundle.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="indie-creators"&gt;Indie creators&lt;a class="zola-anchor" href="#indie-creators"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mathspp.gumroad.com/l/pythonbootcamp/bootcampbf23"&gt;Python Problem-Solving Bootcamp&lt;/a&gt; — 40% off or purchasing power parity discount whichever is greater&lt;/li&gt;
&lt;li&gt;&lt;a href="https://driscollis.gumroad.com/"&gt;Python books by Michael Driscoll&lt;/a&gt; and &lt;a href="https://www.teachmepython.com/"&gt;Teach Me Python Membership&lt;/a&gt; — 33% off with &lt;code&gt;black23&lt;/code&gt; discount code&lt;/li&gt;
&lt;li&gt;&lt;a href="https://adamchainz.gumroad.com/"&gt;Ebooks on Django and Git&lt;/a&gt; — 50% off, plus purchasing power parity if applicable
&lt;ul&gt;
&lt;li&gt;see also author's &lt;a href="https://adamj.eu/tech/2023/11/20/django-black-friday-deals-2023/"&gt;blog post&lt;/a&gt; for links to other Django-related deals&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thepythoncodingplace.thinkific.com/bundles/the-python-coding-place-membership"&gt;The Python Coding Place Membership&lt;/a&gt; — 70% off&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonmorsels.com/lifetime-access-sale/"&gt;Python Morsels Membership&lt;/a&gt; — lifetime access for the price of 2 years
&lt;ul&gt;
&lt;li&gt;see also author's &lt;a href="https://treyhunner.com/2023/11/python-black-friday-and-cyber-monday-sales-2023/"&gt;blog post&lt;/a&gt; for links to other Python deals&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bhavaniravi.gumroad.com/l/LaFSj/P2PBLACK2023"&gt;Python To Projects - 5 Week Online Course&lt;/a&gt; — 25% off&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lernerpython.com/bfcm-2023/"&gt;Python books by Reuven Lerner&lt;/a&gt; — 40% off&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wizardzines.com/"&gt;wizard zines&lt;/a&gt; — 50% off on PDFs, 30% off on print versions&lt;/li&gt;
&lt;li&gt;&lt;a href="https://shrutibalasa.gumroad.com/l/level-up-with-tailwind-css/blackfriday23"&gt;Level up with Tailwind CSS&lt;/a&gt; and &lt;a href="https://shrutibalasa.gumroad.com/l/css-flex-and-grid/blackfriday23"&gt;Complete Guide to CSS Flex and Grid&lt;/a&gt; — 50% off&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="miscellaneous"&gt;Miscellaneous&lt;a class="zola-anchor" href="#miscellaneous"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nostarch.com/blog/2023-holiday-gift-guide"&gt;NoStarch Press&lt;/a&gt; — 35% off with &lt;code&gt;DEALS4DAYS&lt;/code&gt; code&lt;/li&gt;
&lt;li&gt;&lt;a href="https://media.pragprog.com/newsletters/2023-11-17.html"&gt;The Pragmatic Bookshelf&lt;/a&gt; — 40% off on all ebooks and audio books&lt;/li&gt;
&lt;li&gt;&lt;a href="https://deals.manning.com/buy-2-save-50/"&gt;Manning Publications&lt;/a&gt; — save 50% when you buy 2 or more MEAPs, eBooks, pBooks, liveProjects, or liveVideos&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/0x90n/InfoSec-Black-Friday"&gt;InfoSec Hack Friday&lt;/a&gt; — InfoSec related software/tools&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opsdisk.gumroad.com/l/cphlab/blackfriday2023"&gt;The Cyber Plumber's Lab Guide and Interactive Access&lt;/a&gt; — 33% OFF&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mailchi.mp/leanpub/monthly-sale-2023-black-friday"&gt;Leanpub Monthly Sale&lt;/a&gt; and &lt;a href="https://mailchi.mp/leanpub/weekly-sale-2023-black-friday"&gt;Leanpub Weekly Sale&lt;/a&gt; — offers for programming books, bundles and courses&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/trungdq88/Awesome-Black-Friday-Cyber-Monday"&gt;Huge list of awesome deals&lt;/a&gt; — tools, productivity, books, courses, etc&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blackfridaydeals.dev/"&gt;blackfridaydeals.dev&lt;/a&gt; — Hottest Black Friday Deals for Developers&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.diversifytech.com/black-friday-deals-2023-savings-on-tech-books-and-courses/"&gt;Black Friday Deals&lt;/a&gt; — Savings on Tech Books and Courses&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;p&gt;Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Sat, 25 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/programming-deals-2023/</guid></item><item><title>Technologies left behind</title><link>https://deadlime.hu/en/2023/11/24/technologies-left-behind/</link><description>A brief history of the dynamic web from CGI to applications with built-in HTTP servers</description><author>deadlime</author><pubDate>Fri, 24 Nov 2023 19:24:08 GMT</pubDate><guid isPermaLink="true">https://deadlime.hu/en/2023/11/24/technologies-left-behind/</guid></item><item><title>The joy and dilemma of reading</title><link>https://ilearnt.com/blog/thejoyofreading/</link><description>&lt;p&gt;I read a lot of books. Last year I read over 50 books and this year I am heading towards 40 books.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Fri, 24 Nov 2023 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/thejoyofreading/</guid></item><item><title>RSI Trading Signals in Python with TA-Lib: Generate Buy &amp;amp; Sell Signals</title><link>https://blog.adnansiddiqi.me/using-the-rsi-indicator-to-generate-trading-signals-in-python-with-talib/</link><description>&lt;p&gt;This post is the part of trading series. In the past, I gave you a brief intro to Ta-Lib and how it can be used in technical analysis, in this post, I am going to discuss how you can RSI indicator to generate buy or sell signals in Python by using the TA-Lib library. Before I write code about the implementation, let&amp;#8217;s discuss a bit about signal generation and RSI. What Are Signals In the world of trading, signals are cues or indicators that are derived from various kinds of analyses and guide investors on when to buy, sell, or hold shares. They help traders to make informed decisions based on market conditions. What Is A Technical Indicator A technical indicator in trading is a tool that helps investors analyze price trends and make decisions. It&amp;#8217;s like a financial compass, providing insights into market behavior and helping traders identify potential opportunities or risks. What Is the RSI Indicator The RSI (Relative Strength Index) is a momentum oscillator that measures the speed and change of price movements. It ranges from 0 to 100 and is used to identify overbought or oversold conditions in a market. An RSI level below 30 generates buy signals while an RSI level above 70 generates sell signals. Implementation We discussed the main terminologies, now let&amp;#8217;s write code for generating signals based on RSI values. I already discussed the development setup here so you can refer to the previous article about it. One thing, unlike the earlier article where I used the &amp;#8220;yfinance&amp;#8221; API, I am using AlphaVantage APIs because for some reason yfinance APIs are not working for me. On the other hand, Alphavantage provides awesome APIs which also have a free version. I am going to use the endpoint that returns the daily data of a given symbol. All you have to do is to register for the free API KEY. So&amp;#8230; the idea is, to get the daily data of a certain stock using AlphaVantage API and then use TaLib&amp;#8217;s RSI function to calculate RSI in a pandas dataframe using the default 14-day window. I am using jupyter notebook as it makes things easier. The very first thing is the import of libraries. # All Imports from datetime import datetime as dt import requests import talib as ta import pandas as pd import matplotlib.pyplot as plt The next thing is using AlphaVantage&amp;#8217;s API to get the OHLC data. def get_daily_ohlc_data(symbol): function_name = 'TIME_SERIES_DAILY' base_url = 'https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&amp;#38;symbol={}&amp;#38;apikey={}'.format(symbol,API_KEY) r = requests.get(base_url) data = r.json()["Time Series (Daily)"] return data ohlc = get_daily_ohlc_data("MSFT") # Convert to DataFrame df = pd.DataFrame.from_dict(ohlc, orient="index") # Rename columns df = df.rename(columns={ "1. open": "open", "2. high": "high", "3. low": "low", "4. close": "close", "5. volume": "volume" }) df = df.apply(pd.to_numeric) df = df.sort_index(ascending=True) df The function is pretty simple. Using requests library to call the API and then accessing the relevant dict&amp;#8217;s key to fetch OHLC data of Microsoft&amp;#8217;s stock. After that, convert the payload into a pandas dataframe. Renaming of columns to make it more readable, after that, I am soring them out in ascending order so that I get the latest records at the bottom. When you do that, you will get something like the below. OK, our required dataframe is available, what is left now to calculate RSI def generate_rsi_signals(data, period=14, overbought_threshold=70, oversold_threshold=30): # Calculate RSI data['RSI'] = ta.RSI(data['close'], timeperiod=period) # Initialize 'Signal' column with 'Neutral' data['Signal'] = 'Neutral' # Generate signals data.loc[data['RSI'] &amp;#60; oversold_threshold, 'Signal'] = 'Buy' data.loc[data['RSI'] &amp;#62; overbought_threshold, 'Signal'] = 'Sell' return data In order to calculate RSI, you need to assign a window period, the default value is 14.  In the context of the Relative Strength Index (RSI), the parameter period refers to the number of historical price points used in the calculation. The RSI is a momentum oscillator that measures the speed and change of price movements. The formula for calculating RSI involves comparing the magnitude of recent gains to recent losses over a specified time period. So, calling TALIb&amp;#8217;s RSI function for the period of 14, we add another column in our dataframe, named, RSI RSI is calculated but things do not end here, the next thing we have to do is to generate signals: Buy, Sell, and Neutral. Then the threshold&amp;#8217;s value is to generate buy or sell signals. df_rsi = generate_rsi_signals(df) df_rsi.tail(17) When the above function is called and fetches the last 15 or 17 values, you will get a dataframe like the one below: As you can see, it generated different signals over the period of time, it generated a sell signal where the RSI was more than 70 otherwise neutral. Usually, when you create a trading bot that runs all the time during trading hours, you pick the very last value to decide whether you wanna buy, sell, or hold. Here, the very last value is Neutral or hold thus we will not take any action. Have a winning trading strategy and want it to trade for you automatically, without lifting a finger? I specialize in turning your strategies into fully automated trading bots—whether for stocks, crypto, or forex. Let your strategy work around the clock and maximize your potential. Ready to bring it to life? Email me at kadnan @ gmail.com Conclusion In this post, you learned how easily you can access stock data and then calculate RSI for generating buy and sell signals and minting money ???? Like always, the code is available on GitHub. If you like this post, you should subscribe to my blog for future updates. * indicates required Email Address *&lt;/p&gt;
The post &lt;a href="https://blog.adnansiddiqi.me/using-the-rsi-indicator-to-generate-trading-signals-in-python-with-talib/"&gt;RSI Trading Signals in Python with TA-Lib: Generate Buy &amp; Sell Signals&lt;/a&gt; first appeared on &lt;a href="https://blog.adnansiddiqi.me"&gt;Adnan's Random bytes&lt;/a&gt;.</description><author>Adnan's Random bytes</author><pubDate>Fri, 24 Nov 2023 12:00:42 GMT</pubDate><guid isPermaLink="true">https://blog.adnansiddiqi.me/using-the-rsi-indicator-to-generate-trading-signals-in-python-with-talib/</guid></item><item><title>Intro to Large Language Models (video)</title><link>https://nicolaiarocci.com/intro-to-large-language-models-video/</link><description>&lt;p&gt;Andrej Karpathy has a very well-done &lt;a href="https://youtu.be/zjkBMFhNj_g?si=5tJNFaDcK-FBWnWK"&gt;Intro to Large Language Models&lt;/a&gt;
video on YouTube. As a founding member and research scientist at OpenAI and with a two-year hiatus working on Tesla
Autopilot, Karpathy is an authority in the field. He is also good at explaining hard things.&lt;/p&gt;
&lt;p&gt;As a Kahneman reader, I appreciated the &lt;em&gt;Thinking Fast and Slow&lt;/em&gt; analogy proposed at about half-length in the video:
&amp;ldquo;System 1&amp;rdquo; (fast automatic thinking, rapid decisions) is where we&amp;rsquo;re now; &amp;ldquo;System 2&amp;rdquo; (rational, slow thinking, complex
decisions) is LLMs next goal. Also, I suspect Karpathy&amp;rsquo;s intriguing idea of LLMs as the center of a new &amp;ldquo;operating
system style&amp;rdquo; is not too far off from what will emerge soon. The final segment on AI security and known attack vectors
(jailbreaking, prompt injection, data poisoning) is also super interesting.&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Fri, 24 Nov 2023 08:05:25 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/intro-to-large-language-models-video/</guid></item><item><title>Which Japanese SIM to pick? An IT professional's opinion (Ahamo, Rakuten Mobile, Povo)</title><link>https://david.coffee/japanese-sim-cards/</link><description>&lt;p&gt;&lt;img src="https://david.coffee/japanese-sim-cards/banner.png" /&gt;&lt;/p&gt;&lt;p&gt;I was discussing Japanese SIM cards with a friend the other day and noticed that there aren&amp;rsquo;t really any English resources to help people choose the right SIM card. There is quite a big difference in what you get depending on the provider, so I thought I&amp;rsquo;d add my 2 cents.&lt;/p&gt;
&lt;h2 id="not-all-sims-are-equal"&gt;Not all SIMs are equal&lt;/h2&gt;
&lt;p&gt;If we turn back the clock a couple years - the main carrier SIMs (docomo, softbank, AU) were &lt;strong&gt;expensive&lt;/strong&gt; and predatory. It was easy to get hooked into a 2-year contract costing around 8000 yen a month just for a good data connection. The best you could do was getting something like IIJMO (or a dozen other MVNO options) that would be on the docomo or AU wholesale network, but never be on-par with main-network speed and reliability, often lacking big features like Apple Watch support or 5G.&lt;/p&gt;
&lt;p&gt;MVNOs, or &amp;ldquo;Mobile Virtual Network Operators,&amp;rdquo; are virtual operators that don&amp;rsquo;t own their infrastructure or network. These companies, like IIJMO, Mineo, HIS Mobile, have agreements with main network operators (docomo, au, SoftBank, Rakuten) to resell part of their wholesale network, usually at cheaper rates but with varying quality.&lt;/p&gt;
&lt;p&gt;But all of that changed when Abe stepped down and Suga took over as prime minister. He made 3 important changes to how the big 3 had to operate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Providers can&amp;rsquo;t SIM-lock phones&lt;/strong&gt; - all phones you buy from providers have to have an option to unlock them at any time without paying a huge amount of money&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Providers can no longer charge a contract cancellation fee&lt;/strong&gt; to spark stronger competition - so switching from, say, docomo to AU is now free of charge&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The big providers must offer a cheap plan&lt;/strong&gt; on their main network that is not MVNOs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This changed the entire market: Carriers can no longer lock customers into contracts for years and need to actually offer something unique, otherwise customers will leave.&lt;/p&gt;
&lt;p&gt;The big 3 reacted by first offering cheaper main-network SIMs through their sub-brands (&lt;strong&gt;Y!Mobile&lt;/strong&gt; for Softbank, &lt;strong&gt;UQ Mobile&lt;/strong&gt; for AU). This was already great, because now we had plans that were owned by the big carriers themselves, but the government wasn&amp;rsquo;t happy about that, and told them again: you have to offer cheaper plans on your main brand!&lt;/p&gt;
&lt;p&gt;This gave us 3 new plans: &lt;strong&gt;Ahamo&lt;/strong&gt; from docomo, &lt;strong&gt;Povo&lt;/strong&gt; from AU and &lt;strong&gt;LINEMO&lt;/strong&gt; from SoftBank. The important point here is that all these plans, as I just mentioned, are owned by the big 3, so they are not MVNOs that buy wholesale and piggyback. You get the &lt;em&gt;same&lt;/em&gt; speed and network quality as &amp;ldquo;normal&amp;rdquo; customers of docomo/au/softbank, with no worse deal. These SIMs will almost always be better than any of the MVNOs available!&lt;/p&gt;
&lt;p&gt;So as a first line to choose from, my recommendations would be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;On SoftBank: &lt;strong&gt;LINEMO&lt;/strong&gt;, &lt;strong&gt;Y!Mobile&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;On NTT Docomo: &lt;strong&gt;Ahamo&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;On KDDI/AU: &lt;strong&gt;Povo&lt;/strong&gt;, &lt;strong&gt;UQ Mobile&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;On Rakuten Mobile: &lt;strong&gt;Rakuten Mobile&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For today&amp;rsquo;s post I will be focusing on &lt;strong&gt;&lt;a href="https://ahamo.com"&gt;Ahamo&lt;/a&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;a href="https://povo.jp"&gt;Povo&lt;/a&gt;&lt;/strong&gt; (Promo code: &lt;code&gt;LCOD14FW&lt;/code&gt;) and &lt;strong&gt;&lt;a href="https://r10.to/hN5zpk"&gt;Rakuten Mobile&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="a-quick-look-at-the-networks"&gt;A quick look at the networks&lt;/h2&gt;
&lt;p&gt;Ahamo runs on docomo, Povo on AU, and Rakuten Mobile on its own network.&lt;/p&gt;
&lt;p&gt;In general, &lt;strong&gt;docomo has the strongest network&lt;/strong&gt; with the fastest speed, &lt;strong&gt;followed by AU&lt;/strong&gt;. Rakuten is the new kid on the block, and while their network is steadily growing, it&amp;rsquo;s underwhelming for speed and coverage. There are always patches where one is better than the other, but in general, docomo takes the lead. At the time of writing, the AU network has the best coverage on 5G, with docomo lagging slightly behind.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rakuten Mobile&lt;/strong&gt; has a unique roaming agreement with AU, so in places where Rakuten Mobile isn&amp;rsquo;t available yet, it falls back to some offering from AU. This is NOT the same network as main AU or Povo. It is part of the wholesale network that is being rented out to MVNOs. I couldn&amp;rsquo;t test this as it happens automatically, but I often noticed that my AU line had reception while Rakuten Mobile did not.&lt;/p&gt;
&lt;p&gt;Rakuten Mobile often shows &amp;lsquo;5G&amp;rsquo; connection, but the speed and throughput is far from it, often comparable with lower 4G speed. Yes, the connection to the tower is 5G, but that doesn&amp;rsquo;t mean anything if you don&amp;rsquo;t get 5G benefits; it just eats more battery. This was the only provider where I actually felt that when I entered a URL, I often had to wait a few seconds before anything actually happened, especially in buses or trains.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bonus points:&lt;/strong&gt; Docomo/Ahamo allows you to purchase an extra option (currently costing 0 yen/month) that enables &lt;a href="https://www.docomo.ne.jp/service/5g_sa/"&gt;5G SA (5G standalone)&lt;/a&gt; that moves you to a dedicated 5G core network, and gives you ridiculous speeds, currently claimed at &lt;em&gt;受信実効速度は170Mbps~484Mbpsです。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;
&lt;img alt="5G SA(Standalone)とはの画像" class="img-fluid" src="./img_02_pc.png" /&gt;

&lt;/p&gt;
&lt;p&gt;Only docomo offers 5G SA currently, and the addon is still free for an unspecified amount of time. This is by far the best in terms of speed if that&amp;rsquo;s what you&amp;rsquo;re after.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;However,&lt;/strong&gt; while docomo has the best network, it&amp;rsquo;s also used by a lot of people. So in very crowded areas like at festivals or concerts, it&amp;rsquo;s usually the worst performing. Ironically, Rakuten is the best for those because it has the smallest customer base.&lt;/p&gt;
&lt;h2 id="data-volume-and-plans"&gt;Data volume and plans&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Rakuten Mobile&lt;/strong&gt; has the most attractive deal here. It&amp;rsquo;s unlimited, and the price goes up depending on how much data you use, but is capped at 3000 yen for everything beyond 20GB.&lt;/p&gt;
&lt;p&gt;
&lt;img alt="毎月のデータ利用量でお支払い金額が決まる 20GB超過後無制限※2で2,980円/月(税込3,278円)さらに使わなければ勝手に安くなる! ※2 公平なサービス提供のため速度制限する場合あり。環境により速度低下する場合あり ※通話料等別費用" class="img-fluid" src="./img-saikyo-price-data-pc.png" /&gt;

&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ahamo&lt;/strong&gt; has 2 options: 20gb for 3000 yen, or 100gb for 5000 yen. If you hit the upper limit you get speed limited to 1Mbps which is still pretty good.&lt;/p&gt;
&lt;p&gt;
&lt;img alt="image-20231124142258682" class="img-fluid" src="./image-20231124142258682.png" /&gt;

&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Povo&lt;/strong&gt; has the most unique offering here: Toppings!&lt;/p&gt;
&lt;p&gt;By default, if you pay 0 yen a month, you get 128kbps. That&amp;rsquo;s very slow, but it&amp;rsquo;s free and unlimited. Though you won&amp;rsquo;t be able to do much outside of messaging with it. But did I mention it&amp;rsquo;s free?&lt;/p&gt;
&lt;p&gt;Then you can activate different toppings depending on how much data you need&lt;/p&gt;
&lt;p&gt;
&lt;img alt="IMG_0411" class="img-fluid" src="./IMG_0411-0803492.png" /&gt;

&lt;/p&gt;
&lt;p&gt;As with the others, there is a 20GB plan for ~3000 yen, but more interesting options like unlimited for 24h for 330 yen, or 150 GB for 13,000 yen, valid for 6 months.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been using this SIM a lot on my iPad for its 0 yen option, and then when I have a day of heavy working ahead of me, I buy the unlimited topping and tether to my other devices.&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Provider&lt;/th&gt;
          &lt;th&gt;3GB&lt;/th&gt;
          &lt;th&gt;20GB&lt;/th&gt;
          &lt;th&gt;100GB&lt;/th&gt;
          &lt;th&gt;Unlimited&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;Rakuten Mobile&lt;/td&gt;
          &lt;td&gt;980 yen&lt;/td&gt;
          &lt;td&gt;2980 yen&lt;/td&gt;
          &lt;td&gt;2980 yen&lt;/td&gt;
          &lt;td&gt;2980 yen&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Povo&lt;/td&gt;
          &lt;td&gt;990 yen&lt;/td&gt;
          &lt;td&gt;2700 yen&lt;/td&gt;
          &lt;td&gt;4000 yen (50GB) * 2 or 7400 (200GB / 2)&lt;/td&gt;
          &lt;td&gt;330 * days&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Ahamo&lt;/td&gt;
          &lt;td&gt;2970 yen&lt;/td&gt;
          &lt;td&gt;2970 yen&lt;/td&gt;
          &lt;td&gt;4950 yen&lt;/td&gt;
          &lt;td&gt;-&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Considering this, Rakuten Mobile appears to be the most cost-effective as it never exceeds 2980 yen. However, its network quality is considerably lower. Using a plan like 150 GB for 6 months with Povo can drastically reduce the cost if you don&amp;rsquo;t use the full 20 GB each month anyway. It&amp;rsquo;s also a great sub-SIM because of the 0 yen base plan, and you can always add more data when you need it.&lt;/p&gt;
&lt;p&gt;Ahamo is the least complicated - either 20 GB or 100 GB. 20 GB was always enough for me, but having the option to upgrade to 100 GB when needed is very nice, and 1 Mbps is still not bad when it runs out.&lt;/p&gt;
&lt;h2 id="roaming-when-abroad"&gt;Roaming when abroad&lt;/h2&gt;
&lt;p&gt;Roaming is often inconvenient and expensive. It&amp;rsquo;s usually better to just get a local SIM or eSIM through something like &lt;a href="https://ref.airalo.com/21Qr"&gt;Airalo&lt;/a&gt; and be done with it, but with those new plans we may no longer need to do that.&lt;/p&gt;
&lt;p&gt;Ahamo surprised when it got announced that it comes with 20 GB of data roaming free of charge!! That&amp;rsquo;s honestly bonkers and means that in most countries, you can just leave the SIM active and use free data roaming without any additional steps.&lt;/p&gt;
&lt;p&gt;This offer is limited to 14 days of constant roaming. If you are longer than 14 days outside of Japan, the speed will get reduced to 128 kbps. You&amp;rsquo;ll still have unlimited data abroad for messaging, but yeah, it&amp;rsquo;s gonna be pretty slow. You also can&amp;rsquo;t purchase additional data when it runs out. You have to return to Japan for it to refresh.&lt;/p&gt;
&lt;p&gt;Next in line is Rakuten Mobile - it also gives you 128 kbps unlimited at slow speeds when abroad, or 2 GB at fast speeds free of additional charge (you can toggle fast/slow within the app). Additional volume is available at 1 GB for 500 yen. Still not bad, especially with unlimited at 128 kbps.&lt;/p&gt;
&lt;p&gt;Povo is sadly quite bad. They tried to continue with the topping model but all of the options available are pretty pricey - like 5 GB for 14 days for 7080 yen or 3 GB for 7 days for 4280 yen&amp;hellip; yeah don&amp;rsquo;t use this SIM abroad.&lt;/p&gt;
&lt;h2 id="other-special-perks-apple-watch-carrier-pay-etc"&gt;Other special perks (Apple Watch, carrier pay, etc)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Ahamo&lt;/strong&gt; has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Carrier pay (pay through your phone bill)&lt;/li&gt;
&lt;li&gt;Apple Watch data at 500 yen/month&lt;/li&gt;
&lt;li&gt;5G SA (mentioned above)&lt;/li&gt;
&lt;li&gt;Free 20 GB international data roaming at no extra cost&lt;/li&gt;
&lt;li&gt;5 min of each phone call is free&lt;/li&gt;
&lt;li&gt;Free use of docomo wifi hotspots&lt;/li&gt;
&lt;li&gt;Synergy if you use d払い&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Povo&lt;/strong&gt; has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No Apple Watch :(&lt;/li&gt;
&lt;li&gt;Carrier Pay (AUかんたん決済)&lt;/li&gt;
&lt;li&gt;Unlimited data at 330 yen&lt;/li&gt;
&lt;li&gt;Unlimited data at 128 kbps at actual 0 yen base plan&lt;/li&gt;
&lt;li&gt;Free use of AU wifi hotspots, Wi2Premium and a few more&lt;/li&gt;
&lt;li&gt;Synergy if you use AU Pay&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Rakuten Mobile&lt;/strong&gt; has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Apple Watch data at 500 yen/month&lt;/li&gt;
&lt;li&gt;Free international data roaming for 2 GB, unlimited at 128 kbps&lt;/li&gt;
&lt;li&gt;Actual unlimited plan for cheap&lt;/li&gt;
&lt;li&gt;Carrier Pay&amp;hellip;? at some places (Google Play Store), but most don&amp;rsquo;t support it&lt;/li&gt;
&lt;li&gt;Ability to activate or re-issue an eSIM within ~2 minutes online&lt;/li&gt;
&lt;li&gt;Free phone calls, SMS through their apps&lt;/li&gt;
&lt;li&gt;Gives you extra points on Rakuten Ichiba, synergy with other Rakuten products like Rakuten Card&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;*Rakuten Mobile offers &amp;ldquo;free phone calls and free SMS&amp;rdquo; but it&amp;rsquo;s the weirdest thing. SMS only works on Android if you use their Rakuten Link app, which for whatever reason doesn&amp;rsquo;t offer free SMS on iPhone. Free calls is also only if you use the Rakuten Link app, so it&amp;rsquo;s VOIP and not true cellular - it&amp;rsquo;s often very janky.&lt;/p&gt;
&lt;p&gt;Rakuten Mobile also just stopped working in the past when a new iPhone got released, and some Android phones aren&amp;rsquo;t even supported for whatever reason (have you ever seen a physical SIM not supported on a phone??), so it&amp;rsquo;s by far the buggiest of the providers. They also constantly try to upsell your some other Rakuten product, even their Rakuten Link app is plastered with ads&amp;hellip;&lt;/p&gt;
&lt;h2 id="so-david-which-one"&gt;So David, which one?&lt;/h2&gt;
&lt;p&gt;In the end it depends on what you want, no? But if you&amp;rsquo;re still struggling with deciding which one fits the best, my recommendation would be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ahamo&lt;/strong&gt; if you just want 20 GB at fast speed, have an Apple Watch and/or travel a lot. The 20 GB roaming is unbeatable, so is 5G SA. 100 GB for 5000 yen is still reasonable for when you need it. The best all-around SIM.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Povo&lt;/strong&gt; for their unique topping mechanism and still &lt;em&gt;very&lt;/em&gt; good network, even better than docomo in some areas and crowded places, or with 5G coverage. The 0 yen base plan makes this a no-brainer to have &amp;lsquo;just in case&amp;rsquo;, or put onto an iPad to always have a 128 kbps data connection on hand. 330 yen for unlimited when needed is damn good for remote working. No Apple Watch option though, and their roaming offering sucks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rakuten Mobile&lt;/strong&gt; if you want something unlimited and don&amp;rsquo;t care much about network, speed, throughput or coverage. It&amp;rsquo;s good enough in cities and a great secondary SIM to have for when Docomo or AU are overloaded, or when travelling. The ability to quickly move the eSIM to another device has been a life-saver. But, as you can tell from the post, the network is the most &amp;lsquo;meh&amp;rsquo;, has high latency, lots of dark spots, and other weirdness like their Rakuten Link app having different behavior on iPhone or Android. Also I &lt;em&gt;hate&lt;/em&gt; that they constantly try to upsell me when I&amp;rsquo;m already a customer.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the end, just try them and see which one you like the most. There is no contract cancellation fee anymore, so it&amp;rsquo;s very easy to move a number from plan A to plan B and back.&lt;/p&gt;
&lt;p&gt;If you liked this post, please us my referral codes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Povo: &lt;code&gt;LCOD14FW&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Rakuten Mobile: &lt;a href="https://r10.to/hN5zpk"&gt;https://r10.to/hN5zpk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Airalo: &lt;a href="https://ref.airalo.com/21Qr"&gt;https://ref.airalo.com/21Qr&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>David Mohl</author><pubDate>Fri, 24 Nov 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://david.coffee/japanese-sim-cards/</guid></item><item><title>Town Hall #16: Bloat</title><link>https://taylor.town/town-hall-0016</link><description>Rock the bloat!</description><author>taylor.town</author><pubDate>Fri, 24 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/town-hall-0016</guid></item><item><title>Company Missions are Bullshit</title><link>https://nelson.cloud/company-missions-are-bullshit/?ref=rss</link><description>Every company&amp;rsquo;s real mission is to make money.</description><author>Nelson Figueroa</author><pubDate>Fri, 24 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://nelson.cloud/company-missions-are-bullshit/?ref=rss</guid></item><item><title>Working with feature flags</title><link>https://yasha.solutions/posts/working-feature-flags/</link><description>some notes about feature flags benefits and implementation</description><author>Yasha Solutions</author><pubDate>Fri, 24 Nov 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://yasha.solutions/posts/working-feature-flags/</guid></item><item><title>English-only embedding models for multilingual docs</title><link>https://geo.rocks/post/embedding-en-models-multilingual-support/</link><description>&lt;p&gt;&lt;img alt="SemanticFinder bge-large-en performance" src="https://geo.rocks/img/bge-large-en.png" /&gt;&lt;/p&gt;
&lt;p&gt;Text Embeddings: Speaking languages without learning them?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt;: some *-en models perform well on other languages too.&lt;/p&gt;</description><author>Geography &amp;amp; Coding</author><pubDate>Thu, 23 Nov 2023 21:33:13 GMT</pubDate><guid isPermaLink="true">https://geo.rocks/post/embedding-en-models-multilingual-support/</guid></item><item><title>How to do Alt Data Research</title><link>https://magis.substack.com/p/how-to-do-alt-data-research</link><description>A practical guide to succeeding in alternative data</description><author>Magis</author><pubDate>Thu, 23 Nov 2023 20:54:27 GMT</pubDate><guid isPermaLink="true">https://magis.substack.com/p/how-to-do-alt-data-research</guid></item><item><title>In life as in gaming, CHA is the strongest stat</title><link>https://blog.vslira.net/2023/11/in-life-as-in-gaming-cha-is-strongest.html</link><description>As I write this post, the board of directors of OpenAI is under extreme pressure to reinstate Sam Altman as CEO and giving him complete control of the company while they're at it. If they don't do that, it's likely that they'll lose the company's financial backers, compute enablers and a significant portion of their most important employees - the proverbial members of technical staff. Oh, and be sued to high Heaven by Satya &amp;nbsp;Nadella.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Far from accusing Sam of being just talk as the title of this post may imply, but however intelligent he is what he brought for the company certainly wasn't his AI development capabilities, but the external and internal support that the company needed to move forward.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Contrary to everything I would like to be true, this skill - convincing people, selling to them - is more important than being able to build what they want. Technically being able to, I mean, because actually forming a team to deliver, and presenting it in a way that convinces people to give you attention, will always be more effective than trying to do it on your own and hoping the interested people just show up waving their money at you.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Now, I'm not implying that the board was morally right or that Mr "I Love You All" programmed GPT-N by himself on his basement or anything like that. I'm saying that EVEN IF that was true, Sam was the face of the company, he was the one with the whole support. Not the board, who technically just did what it could always do, for reasons that might not even be wrong according to its charter. But legal abstractions, while certainly not meaningless, can only do so much.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;This is a bitter pill for nerds dreaming about their technical startups to swallow, but doesn't make it any less true. Even if you don't want to open your own company and just want to raise the ranks within your current workplace by technical prowess alone, I assure you it will be a thousand times easier if you manage to be a little bit more like &lt;a href="https://x.com/BIGPIKLIZ/status/1725144981684273246"&gt;this girl&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;And if you nominally have the position and power you want, don't forget to occasionally ask yourself, in Bane's voice: "Do you feel in charge?"&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Addendum 2023-11-23: &lt;a href="https://x.com/robinhanson/status/1727670794782728574?s=20"&gt;This&lt;/a&gt; might be relevant to whoever finds the text above useful&lt;/div&gt;</description><author>vslira's blog</author><pubDate>Thu, 23 Nov 2023 15:01:32 GMT</pubDate><guid isPermaLink="true">https://blog.vslira.net/2023/11/in-life-as-in-gaming-cha-is-strongest.html</guid></item><item><title>(fake) Football Leagues</title><link>https://rjp.is/blogging/posts/2023/11/football-leagues/</link><description>In which we fake some leagues for fun.</description><author>infrequent oscillations</author><pubDate>Thu, 23 Nov 2023 14:01:32 GMT</pubDate><guid isPermaLink="true">https://rjp.is/blogging/posts/2023/11/football-leagues/</guid></item><item><title>Published IETF RFCs</title><link>https://rob.sh/post/published-rfcs/</link><description>&lt;p&gt;Over the years I have published a number of RFCs in the IETF. They are listed below.&lt;/p&gt;
&lt;p&gt;As primary author:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://tools.ietf.org/html/rfc7684"&gt;RFC7684&lt;/a&gt; - OSPFv2 Prefix/Link Attribute Advertisement&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tools.ietf.org/html/rfc7777"&gt;RFC7777&lt;/a&gt; - Advertising Node Administrative Tags in OSPF&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tools.ietf.org/html/rfc7855"&gt;RFC7855&lt;/a&gt; - Source Packet Routing in Networking (SPRING) Problem Statement and Requirements&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tools.ietf.org/html/rfc8355"&gt;RFC8355&lt;/a&gt; - Resiliency Use Cases in Source Packet Routing in Networking (SPRING) Networks&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tools.ietf.org/html/rfc8370"&gt;RFC8370&lt;/a&gt; - Techniques to Improve the Scalability of RSVP-TE Deployments&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tools.ietf.org/html/rfc8402"&gt;RFC8402&lt;/a&gt; - Segment Routing Architecture&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tools.ietf.org/html/rfc8660"&gt;RFC8660&lt;/a&gt; - Segment Routing with the MPLS Data Plane&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tools.ietf.org/html/rfc8662"&gt;RFC8662&lt;/a&gt; - Entropy Label for Source Packet Routing in Networking (SPRING) Tunnels&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tools.ietf.org/html/rfc8665"&gt;RFC8665&lt;/a&gt; - OSPF Extensions for Segment Routing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As contributor:&lt;/p&gt;</description><author>rob.sh</author><pubDate>Thu, 23 Nov 2023 12:00:00 GMT</pubDate><guid isPermaLink="true">https://rob.sh/post/published-rfcs/</guid></item><item><title>使用子解释器运行Python并行应用</title><link>https://www.4async.com/2023/11/python-312-sub-interpreters/</link><description>&lt;img alt="Featured image of post 使用子解释器运行Python并行应用" src="https://www.4async.com/2023/11/python-312-sub-interpreters/cover.webp" /&gt;&lt;blockquote&gt;
&lt;p&gt;译者注：最近 Python 3.12 引入了子解释器概念，非常火热，更好的消息是已经&lt;a class="link" href="https://twitter.com/anthonypjshaw/status/1723916190202945761" rel="noopener" target="_blank"&gt;在FastAPI应用成功&lt;/a&gt;了，虽然是很简单的那种。因此顺腾摸瓜，找到了作者的&lt;a class="link" href="https://tonybaloney.github.io/posts/sub-interpreter-web-workers.html" rel="noopener" target="_blank"&gt;博客&lt;/a&gt;，翻译分享给大家。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Python 3.12 引入了一个新的 API 用于“子解释器”（sub interpreters），这是 Python 的一种不同的并行执行模型，提供了真正并行处理和多进程处理之间的良好折中，且具有更快的启动时间。在这篇文章中，我将解释什么是子解释器，为什么它对 Python 中的并行代码执行很重要，以及它与其他方法的比较。&lt;/p&gt;
&lt;h2 id="什么是子解释器"&gt;什么是子解释器？
&lt;/h2&gt;&lt;p&gt;Python 的系统架构大致由三部分组成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个包含一个或多个解释器的 Python 进程&lt;/li&gt;
&lt;li&gt;一个包含锁（GIL）和一个或多个 Python 线程的解释器&lt;/li&gt;
&lt;li&gt;一个包含当前执行代码信息的线程。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="interpreter states" class="gallery-image" height="1898" src="./2023/11/python-312-sub-interpreters/interpreter-states-1.png" width="2111" /&gt;&lt;/p&gt;
&lt;p&gt;要了解更多关于这方面的信息，你可以阅读我的书&lt;a class="link" href="https://tonybaloney.github.io/#books" rel="noopener" target="_blank"&gt;《CPython 内部实现》&lt;/a&gt;中的“并行性和并发性”章节。&lt;/p&gt;
&lt;p&gt;自 Python 1.5 以来，就有一个 C-API 可以支持多个解释器，但这个功能由于 GIL 的限制而受到严重限制，没有真正实现真正的并行性。因此，运行并行代码最常用的技术（不使用第三方库）是使用 &lt;a class="link" href="https://docs.python.org/3/library/multiprocessing.html" rel="noopener" target="_blank"&gt;multiprocessing 模块&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;2017 年，CPython 核心开发人员提出改变解释器结构的提议，使它们更好地与拥有它们的 Python 进程隔离，并能够并行操作。实现这一目标的工作相当巨大（6 年后仍未完成），并分为两个 PEP。PEP684 将 GIL 在各个解释器独立开，PEP554 提供了一个创建解释器和在它们之间共享数据的 API。&lt;/p&gt;
&lt;p&gt;GIL 是“全局解释器锁”，是 Python 进程中的一个锁，意味着在任何时间点 Python 进程中只能执行一条指令，即使它有多个线程。这实际上意味着，即使你在拥有 4 核 CPU 的电脑上同时启动 4 个 Python 线程，也只有一个线程会在任何时候运行。&lt;/p&gt;</description><author>ipfans's Blog</author><pubDate>Thu, 23 Nov 2023 11:39:00 GMT</pubDate><guid isPermaLink="true">https://www.4async.com/2023/11/python-312-sub-interpreters/</guid></item><item><title>Lua Carousel</title><link>http://akkartik.name/post/carousel</link><description>&lt;p&gt;
I finally decided to hang up a shingle on itch.io. My first app there is not a
game. &lt;a href="https://akkartik.itch.io/carousel"&gt;Lua Carousel&lt;/a&gt; is a
lightweight environment for writing small, throwaway Lua and LÖVE programs.
With many thanks to Mike Stein who helped me figure out how to get it working
on iOS, this is my first truly cross-platform app, working on Windows, Mac,
Linux, iOS and Android.

&lt;p&gt;
&lt;img alt="screenshot" src="https://akkartik.name/images/20231123-lua-carousel.png" style="width: 100%;" /&gt;

&lt;!-- more --&gt;

&lt;p&gt;
&lt;a href="https://git.sr.ht/~akkartik/carousel.love"&gt;repo&lt;/a&gt;

&lt;p&gt;
Carousel has its own devlog/notebook. I try to post little scripts that are
easy for someone to copy to their clipboard, paste into Carousel and run. Some
examples:
&lt;ul&gt;
&lt;li&gt; &lt;a href="https://akkartik.itch.io/carousel/devlog/645189/version-av-one-bugfix-support-for-lve-115"&gt;Hilbert curve and Lindenmayer tree fractal&lt;/a&gt; (70 lines each)
&lt;li&gt; &lt;a href="https://akkartik.itch.io/carousel/devlog/647054/2-days-um-no-0-days-since-last-bug"&gt;rectangle art&lt;/a&gt; (40 lines)
&lt;li&gt; &lt;a href="https://akkartik.itch.io/carousel/devlog/647726/playing-with-lves-physics-engine-for-the-first-time"&gt;simple example using the physics engine.&lt;/a&gt; (100 lines)
&lt;li&gt; &lt;a href="https://akkartik.itch.io/carousel/devlog/651711/new-version-after-9-days"&gt;Chromacline: a war between colors in Game of Life world&lt;/a&gt; (100 lines)
&lt;li&gt; &lt;a href="https://akkartik.itch.io/carousel/devlog/653245/the-lua-carousel-productivity-suite"&gt;simple UI examples; simple voice recorder&lt;/a&gt; (&lt;50 lines each)
&lt;li&gt; &lt;a href="https://akkartik.itch.io/carousel/devlog/656473/building-an-equation-plotter"&gt;sprite editor&lt;/a&gt; (100 lines)
&lt;li&gt; &lt;b&gt;&lt;a href="https://akkartik.itch.io/carousel/devlog/673935/pong-wars"&gt;Pong wars&lt;/a&gt;&lt;/b&gt; (70 lines)
&lt;li&gt; &lt;a href="https://akkartik.itch.io/carousel/devlog/678890/new-version-after-51-days"&gt;equation plotting over an infinite surface with pan and pinch-zoom&lt;/a&gt; (150 lines)
&lt;li&gt; &lt;a href="https://akkartik.itch.io/carousel/devlog/686788/lots-of-charts"&gt;Lots of different kinds of charts&lt;/a&gt; (200 lines)
&lt;/ul&gt;</description><author>Kartik Agaram</author><pubDate>Thu, 23 Nov 2023 09:00:00 GMT</pubDate><guid isPermaLink="true">http://akkartik.name/post/carousel</guid></item><item><title>I am speaking at the .NET Conference Italia 2023</title><link>https://nicolaiarocci.com/i-am-speaking-at-the-dotnet-conference-italia-2023/</link><description>&lt;p&gt;In a few days, on Monday, December 4, at 10 a.m., I will present at the .NET Conference Italia 2023, which will be held
at the Microsoft House in Milan. My session is titled &amp;ldquo;C#12 What&amp;rsquo;s new and interesting&amp;rdquo; and is part of a &lt;a href="https://dotnetconference.it/e/3281/dotNET-Conference-Italia-2023"&gt;rich agenda&lt;/a&gt;
focused on the recent release of .NET 8.&lt;/p&gt;
&lt;p&gt;I presented on the same subject at a &lt;a href="https://www.meetup.com/it-IT/devromagna/"&gt;DevRomagna&lt;/a&gt; meetup a few months ago when C# 12 was in preview and still had
several moving parts. Now that the final release is out, it will be interesting to return to the topic to consolidate my
knowledge. I suspect that of the many reasons I challenge myself to speak publicly, enriching my knowledge is prevalent.
As the adage goes, for learning a subject, nothing beats explaining it in front of a knowledgeable audience.&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Thu, 23 Nov 2023 08:05:25 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/i-am-speaking-at-the-dotnet-conference-italia-2023/</guid></item><item><title>I ❤️ Releases</title><link>https://backendhance.com/blog/2023/i-releases/</link><description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;p&gt;Nach fast zwei Jahren ohne Release ist es am Samstag endlich so weit.&lt;br /&gt;
&lt;strong&gt;Bei einem meiner Kunden wurde seit zwei Jahren nicht released&lt;/strong&gt; - Wahnsinn 🤯&lt;/p&gt;
&lt;p&gt;Warum? Viele Gründe.&lt;/p&gt;
&lt;p&gt;Das Team hat sich in Komplexität verrannt. Es wurden große &lt;em&gt;(notwendige)&lt;/em&gt; Änderungen vorgenommen.&lt;/p&gt;
&lt;p&gt;Diese hatten aber viele Seiteneffekte. Und der Anspruch war alles &amp;ldquo;richtig&amp;rdquo; zu machen. Es wurde also immer weiter entwickelt. Mit einem abgeschlossenen Thema kamen 5 Neue auf.&lt;/p&gt;
&lt;h1 id="aus-diesem-kreislauf-kam-das-team-nicht-raus"&gt;
&lt;a class="anchor" href="#aus-diesem-kreislauf-kam-das-team-nicht-raus"&gt;Aus diesem Kreislauf kam das Team nicht raus.&lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;Als ich im Juli zum Team gerufen wurde beschlossen wir: &lt;strong&gt;Wir müssen diesen Teufelskreis durchbrechen.&lt;/strong&gt;&lt;/p&gt;</description><author>Backendhance</author><pubDate>Thu, 23 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/i-releases/</guid></item><item><title>wigwam.directory</title><link>https://wigwam.directory</link><description>Support wigwam culture!</description><author>taylor.town</author><pubDate>Thu, 23 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://wigwam.directory</guid></item><item><title>Setting up a Raspberry Pi Home Server</title><link>https://jasoneckert.github.io/myblog/pi-home-server/</link><description>&lt;p&gt;&lt;img alt="Raspberry Pi 4" src="pi4.png#right" title="Raspberry Pi 4" /&gt;&lt;/p&gt;
&lt;p&gt;The Raspberry Pi has been the most popular &lt;em&gt;&lt;strong&gt;single board computer (SBC)&lt;/strong&gt;&lt;/em&gt; among computer hobbyists for the past decade. It can be used to perform a wide variety of very different tasks, such as running graphical apps, streaming media, performing home automation, and monitoring humidity levels in a greenhouse. This is largely made possible by the Raspberry Pi&amp;rsquo;s many interfaces (including GPIO, USB, HDMI, and wired/wireless Ethernet), as well as its ability to run the highly-customizable Linux operating system.&lt;/p&gt;</description><author>Jason Eckert's Website and Blog</author><pubDate>Thu, 23 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jasoneckert.github.io/myblog/pi-home-server/</guid></item><item><title>Five minute hacks: Swapping left and right headphone audio in wireplumber</title><link>https://purpleidea.com/blog/2023/11/22/swapping-left-and-right-headphone-audio-in-wireplumber/</link><description>&lt;p&gt;I&amp;rsquo;m on Fedora 37 at the moment. My headphone audio is backwards. How do we fix
it? I&amp;rsquo;ve apparently never found the right magic until now.&lt;/p&gt;


&lt;br /&gt;&lt;span style="text-decoration: underline; font-weight: bold;"&gt;The Setup&lt;/span&gt;:&lt;br /&gt;&lt;br /&gt;

&lt;p&gt;I have a crappy &amp;ldquo;couch computer&amp;rdquo;. It&amp;rsquo;s used for casual internet browsing and
video watching. It has an analog audio out which goes to my speakers. I&amp;rsquo;d like
to use headphones for when I don&amp;rsquo;t want to annoy my neighbours. I have a pair of
wireless headphones that I got for free, but the battery doesn&amp;rsquo;t last longer
than an hour. I also have an old pair of analog headphones that work okay and
don&amp;rsquo;t run out of battery! I got a free USB to analog dongle, but for some reason
the left and right audio channels are reversed!&lt;/p&gt;</description><author>The Technical Blog of James on purpleidea.com</author><pubDate>Thu, 23 Nov 2023 00:25:00 GMT</pubDate><guid isPermaLink="true">https://purpleidea.com/blog/2023/11/22/swapping-left-and-right-headphone-audio-in-wireplumber/</guid></item><item><title>Postmortems in Software Development</title><link>https://vadimkravcenko.com/shorts/dealing-with-failures-and-postmortems/</link><description>&lt;p&gt;Question:&lt;/p&gt;
&lt;/p&gt;
&lt;p&gt; Answer:&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://vadimkravcenko.com/shorts/dealing-with-failures-and-postmortems/"&gt;Postmortems in Software Development&lt;/a&gt; appeared first on &lt;a href="https://vadimkravcenko.com"&gt;Vadim Kravcenko&lt;/a&gt;.&lt;/p&gt;</description><author>Vadim Kravcenko</author><pubDate>Wed, 22 Nov 2023 21:40:42 GMT</pubDate><guid isPermaLink="true">https://vadimkravcenko.com/shorts/dealing-with-failures-and-postmortems/</guid></item><item><title>More native than a Web app</title><link>https://seirdy.one/notes/2023/11/21/more-native-than-web/</link><description>&lt;p&gt;Whenever I discover a new GUI toolkit, my first question is always “is it more native than the Web?” For reference, here are some ways Web apps have better system integration than Flutter:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;System fonts, font hinting settings, and font fallback settings.&lt;/li&gt;&lt;li&gt;System text navigation shortcuts.&lt;/li&gt;&lt;li&gt;System scrollbar visibility, thickness, and click settings/behavior.&lt;/li&gt;&lt;li&gt;System highlight menus and controls (on touchscreens).&lt;/li&gt;&lt;li&gt;System display scaling settings.&lt;/li&gt;&lt;li&gt;System reduced-motion settings (An API is documented, but I have never used a Flutter app that actually disables animations. I may be wrong).&lt;/li&gt;&lt;li&gt;System reduced-transparency settings.&lt;/li&gt;&lt;li&gt;System forced-colors mode (Windows High Contrast Mode).&lt;/li&gt;&lt;li&gt;System color palettes (some browsers support setting default colors without necessarily forcing the palette).&lt;/li&gt;&lt;li&gt;System media controls (play/pause/skip, current track, e.g. using MPRIS on freedesktop platforms).&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;It’s an easier question to answer than my second question: “What &lt;abbr title="Web Content Accessibility Guidelines"&gt;WCAG&lt;/abbr&gt; conformance level can I hope for?&lt;/p&gt;</description><author>All content on Seirdy’s Home</author><pubDate>Wed, 22 Nov 2023 09:22:58 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/notes/2023/11/21/more-native-than-web/</guid></item><item><title>I hereby pardon all junior engineers</title><link>https://taylor.town/pardon-2023</link><description>Shortsighted engineering practices have eroded public trust in technology. We can reclaim that trust by building better things together.</description><author>taylor.town</author><pubDate>Wed, 22 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/pardon-2023</guid></item><item><title>Starfield review</title><link>https://burakku.com/blog/starfield-review/</link><description>&lt;p&gt;&lt;img alt="Starfield" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Mr. Howard's Mild Ride.&lt;/p&gt;
&lt;p&gt;The very first impression I had with Starfield was that the world design looked really good. The art direction of Starfield is great. I'm a big fan of the "NASA punk". However, the further I progressed in the game, the less impressed I got with the world. While the individual aspects of it might look good, it's way too repetitive. You land on a single wasteland planet and you've really landed on all of them. And as far as I can tell, the few points of interest in them are repeated throughout the known universe.&lt;/p&gt;
&lt;p&gt;The cities are pathetic too. There's a couple of different cities with vastly different designs. They do however have one thing in common: the scale. All of the cities feel woefully small. It feels like I'm walking through a movie set than an actual city. And despite the lack of scale, there's loading screens absolutely everywhere. Get out of your space ship – loading screen. Go to a small residential area – loading screen. Go to the weapon store inside the small residential area – loading screen. Thankfully the missions aren't as full of loading screens but it seems strange that these small cities are in a big-budget 2023 release.&lt;/p&gt;
&lt;p&gt;Traversal between places is also deeply unsatisfying. I had to go from a city on a planet, to a bandit camp on the same planet. I opened the extremely poor planet map, selected the bandit camp, and tried to transport there. Sorry, the game said, you can't fast travel to a place you haven't discovered yet. Then it forced me to fast travel to my spaceship and then land my spaceship at the bandit camp. And what does "landing your spaceship" actually entail? A loading screen and a jump cut to the spaceship landing, which all takes around 10 seconds. Almost all traversal in this game is fast travel and yet it still does this idiotic gatekeeping for what it thinks "fast travel" is.&lt;/p&gt;
&lt;p&gt;The space bit in Starfield doesn't really improve the situation at all. You can either talk to whatever NPCs happen to be around you, or engage them in battle. That's it. Despite the lack of anything, you'll still be going to space very often since you need to spend at least a few seconds there between fast travels. Actually, the NPCs either reaching out or attacking got very tiresome since I just wanted to travel to my destination and not play tag with pirates.&lt;/p&gt;
&lt;p&gt;The repetition in the world design also comes across in the story campaign. The story had me go look for Andreja, and I found her in an enemy-infested mine, and at the end of the mine, I found an artefact. Then later in the story, I was tasked to go look for more artefacts, one of which was at the end of an enemy-infested mine. A mine with the exact same layout, except this one was on a different planet and lacked one Andreja. Both of these mines also had a locked locker room, with contraband for the taking after you picked open the lock. While I can understand that the planets look similar when you have such a vast number of them, I expected a lot more from the main story locations in a AAA game.&lt;/p&gt;
&lt;p&gt;Really feels like Bethesda is just taking the piss with the repetition considering missions are actually named "Into the Unknown" and "Further Into the Unknown". A bit on the nose there Todd.&lt;/p&gt;
&lt;p&gt;As for the story itself, I didn't really care for it. First of all, I found the premise of how you became an adventurer quite contrived. And while I think they want to make Constellation feel like some sort of an elite gentleman explorer's club, most of the story missions made me feel like their glorified errand girl instead. For every interesting mission, like Unearthed or Entangled, there were two or three missions where I just had to fly from one remote planet to another, literally digging for quest items. Surely there must've been a more interesting story to write in the Starfield universe, right? And despite the fact that the story involves life and death situations, the writing is just so devoid of any edge or character. The whole thing just feels like such a lifeless and watered down experience.&lt;/p&gt;
&lt;p&gt;&lt;img alt="I love you too, Vasco" src="vasco.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Guess it didn't help that I couldn't care for any of the characters, including my own. The only (human) member of Constellation that I didn't have any active contempt towards was Walter. Vasco was cool too, and got promoted to my primary companion after Sarah got very angry at me for a heinous murder where I shot a man after he and his friends unloaded every bullet they had towards our direction. I guess Sarah was looking to end her miserable existence and I rudely interrupted it by defending myself.&lt;/p&gt;
&lt;p&gt;I've heard that some of the side content is much superior but unfortunately I seem to have never encountered it. I guess I did mainly focus on the main quest so maybe that's on me. However, I did encounter side content that I didn't care for.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Defiant settler captain" src="settler.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;One example is a mission where settlers task me to drive out a corp from a planet that they consider their birthright and one that they seem willing to fight for. I go talk to the corp and they want me to either blow up the settlers or convince them to become serfs on their luxury resort planet. I decided to not blow up these poor settlers and instead opt to tell them about this indentured servitude proposal. Considering how defiant the settler captain acted, I expected this to lead into a war where they take the planet by force. Nope, the settlers turn heel and agree to become slaves. They they asked me to bring them a bunch of materials, presumably so they can make their own leg irons to really enjoy their bondage. I considered blowing them up for their own good but settled to just leave instead.&lt;/p&gt;
&lt;p&gt;I did enjoy the ending though, at least on a conceptual and storytelling level. Unfortunately the gameplay side of it is extremely herky-jerky, and is not at all enhanced by the space combat or waves of trash mobs you also have to deal with. And right at the last second, the story really just raises more questions than it answers. At least the ending was pretty.&lt;/p&gt;
&lt;p&gt;As a game, Starfield is a rather unfocused affair. It's so chock-full of different systems that it feels very overwhelming when you get started. Maybe veterans to Bethesda RPGs have already learned to juggle these systems in prior games, but as a newcomer, you need to ingest a lot of (unhelpful) tutorials at the beginning. You have ground traversal, space traversal, ground combat, ship combat, ship building, outpost building, cooking, crafting, spacesuit upgrading, weapon customisation, mystical powers, lockpicking, persuasion, scanning, mining, stealth, encumbrance, commerce, status effects and so on. "A mile wide and an inch deep" is the best quote I have heard to describe this game. Many of these systems are very badly taught to you, and even when you learn them, they're still not that great. Quite possibly why it also took me around ten hours to actually get into the game properly.&lt;/p&gt;
&lt;p&gt;The ground combat feels fine. There's a decent selection of firearms available to you and they do feel pretty distinct from each other. It's definitely not very flashy, and there's very limited amount of specialisation you can do with the combat but I did generally enjoy engaging the enemy. The boost pack also livens up the movement for the combat sections too.&lt;/p&gt;
&lt;p&gt;The mystical space powers feel like a complete waste of time, at least for enhancing the combat. I really only ever used the unlimited stamina power and otherwise ignored them. And the way that you obtain all of these various powers is so needlessly repetitive. You have to &lt;em&gt;literally&lt;/em&gt; jump through hoops for them, again and again and again. It feels more like an elaborate prank than game design.&lt;/p&gt;
&lt;p&gt;Space combat is less fine. First of all, whenever you engage in ship combat, you were most likely just on your way to do something completely different until someone decided to harass you. And when they do, they usually do it with their pals. In three-dimensional space. I feel like most of the time in combat is spent around doing loops in order to just find one of the three hostile ships so you can blast them, while trying to keep their two friends from doing the same to you. Even though I rarely struggled, I still did not enjoy it.&lt;/p&gt;
&lt;p&gt;Ship building seemed pretty cool, at least for someone that enjoys that kind of a thing. I mainly used it pretty pragmatically, so instead of trying to replicate famous designs or come up with my own, I just used it to upgrade an existing design. It does take some getting used to, since the game doesn't really teach you how to do it and I had to get supplementary lessons from Google on how I can get ship pieces to connect how I want. And unfortunately changing your ship in any way completely destroys any interior decor you might have going on (for understandable technical reasons), so it definitely kept me from doing it just for fun. Maybe people that are heavily into this would find more to criticise about it but I thought that the ship building was cool overall.&lt;/p&gt;
&lt;p&gt;Outpost building is also something that is not extremely well taught to you. And it doesn't really feel like you would ever need to do it. However, once I learned a bit on how it works, I got a frenzy of finding resource-heavy planets, building outposts on them all, and connecting them all together to a master outpost. I got pretty absorbed into it until I had all of my outposts connected. Then I realised that I didn't really have any kind of use for it. It just kinda is there, filling up my inventory and few resource crates with a bunch of minerals that I don't really have an use for. I did my outpost fairly early in my run and had no reason to touch it afterwards.&lt;/p&gt;
&lt;p&gt;Inventory management is also subpar. For some reason Bethesda decided that the majority of the screen should be taken by 3D representations of your items instead of useful information. While the gun models look nice, I don't find myself that interested in knowing what copper ore looks like. I'd much rather have a side-by-side view of my inventory and a vendor's inventory instead of having to flip between two or three different inventories as I unload my stuff. The vendors will also instantly run out of money when you go sell things, so you need to go do your tedious inventory management with every single vendor in the game. At least I did get used to the inventory, so the user interface is not a constant source of pain, but I still pine for something better.&lt;/p&gt;
&lt;p&gt;The lockpicking minigame is actually rather fun but it takes home the prize for the worst explained system in the game. I had no clue on how to do it and was just randomly trying to get through it. Then I watched a short tutorial video on YouTube and got it. Now I'm pretty good at picking open the locks and can actually enjoy some momentary tinkering with locks. What a shame the game doesn't want to teach you about it.&lt;/p&gt;
&lt;p&gt;Not a big fan of character creation either. It feels like some of the aspects of it are very varied and some are barely varied at all. I would've also enjoyed having a voice for my character. There's one part in the story that actually makes me feel like it would've been very reasonable and feasible thing even. And I really don't understand the point of the traits. I remember people criticising the lifepaths in Cyberpunk 2077 for having little impact on the game, but I feel like the traits in Starfield have even less of an impact. I was a Neon Street Rat and it just gave me a couple of new choices in one particular part of the game.&lt;/p&gt;
&lt;p&gt;Don't really like the skill trees either, especially how it locks some basic abilities in the game behind a skill point. Or many skill points for things in the advanced tiers of the skill tree. I'd expect to be able to do at least some stealth without investing points to a stealth skill but alas. You also need to level up your skills, so don't even think about reaching rank 4 in Medicine without spending time harming yourself and then ingesting health kits by the dozen.&lt;/p&gt;
&lt;p&gt;Gatekeeping parts of the game by hiding them in the skill tree are also why I never really got into the crafting and upgrading aspect of the game. You have a few things open at the start of the game but anything beyond that requires investing your few skill points. A shame, since I would've liked to upgrade my weapons more, but Weapon Engineering is an advanced Science skill, so I'd need to invest and level up my science skills before I even got a single point in.&lt;/p&gt;
&lt;p&gt;Technical aspects of the game are a bit of a mixed bag. The game was surprisingly stable and bug-free for a new AAA release, although neither aspect was perfect. My crew sank into my spaceship more times than I remember to count and when the game decided to crash, it screwed me out of a lot of progress. At least Cyberpunk 2077 was polite enough autosave very frequently to make up for its lack of stability, and wouldn't make me lose 30 minutes of gameplay with a single hard crash. The performance seems relatively poor considering the lack of ray tracing. Considering how Cyberpunk 2077 looks a lot better with crazy ray tracing, Starfield doesn't deliver that much of a higher frame rate.&lt;/p&gt;
&lt;p&gt;It does seem like they did manage to improve it a bit with the recent patch though, but I've yet to clock any serious amount of time in it to make a judgement. Fingers crossed!&lt;/p&gt;
&lt;p&gt;I tend to avoid pre-release marketing since I want to temper my expectations for new stuff, but I'm still left feeling disappointed. It's not that Starfield is an awful game. I did manage to beat it after all. It just feels really mediocre and undaring. Perhaps it's worth taking a stab at if you were already paying for Game Pass, or found it in a bargain bin, but for its pain points and overwhelming sense of mediocrity, it's easy to skip and hard to recommend.&lt;/p&gt;</description><author>ブラック</author><pubDate>Wed, 22 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/starfield-review/</guid></item><item><title>Jack and Jill</title><link>https://one.mikro2nd.net/pages/jack-and-jill/</link><description>Jack and Jill, Went up the hill To fetch a pail of water. Who goes up a hill to fetch a pail of water? Streams and wells are not generally found up a hill, and for a very simple reason: Water flows, seeps, trickles&amp;hellip; down hills, into dales and hollows and fens, emerging from springs and wells and joining streams that become rivers that wend their way to the sea. You go down the hill to find water.</description><author>one mikro2nd</author><pubDate>Wed, 22 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://one.mikro2nd.net/pages/jack-and-jill/</guid></item><item><title>The Knight Capital Disaster</title><link>https://specbranch.com/posts/knight-capital/</link><description>&lt;p&gt;&lt;em&gt;This account comes from several publicly available sources as well as accounts from insiders who worked at
Knight Capital Group at the time of the issue. I am telling it second- or third-hand.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;On August 1, 2012, Knight Capital fell on its sword. It experienced a software glitch that literally bankrupted the
company.  Between 9:30 am and 10:15 am EST, the employees of Knight capital watched in disbelief and scrambled to
figure out what went wrong as the company acquired massive long and short positions, largely concentrated in 154
stocks, totaling 397 million shares and $7.65 billion.  At 10:15, the kill switch was flipped, stopping the
company's trading operations for the day.  By early afternoon, many of Knight Capital's employees had already
sent out resumes, expecting to be unemployed by the end of the week.&lt;/p&gt;</description><author>Speculative Branches</author><pubDate>Wed, 22 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://specbranch.com/posts/knight-capital/</guid></item><item><title>Exploring what is inside the ASP.NET Core cookies</title><link>https://nestenius.se/net/exploring-what-is-inside-the-asp-net-core-cookies/</link><description>ASP.NET Core generates various types of cookies, such as authentication, antiforgery, and session cookies. In this blog post, we’ll take a closer look at</description><author>Tore Nestenius</author><pubDate>Wed, 22 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://nestenius.se/net/exploring-what-is-inside-the-asp-net-core-cookies/</guid></item><item><title>Spoofing Objective-C return values on Apple Silicon using LLDB</title><link>https://www.jviotti.com/2023/11/22/spoofing-objective-c-return-values-on-apple-silicon-using-lldb.html</link><description>This article describes how LLDB can be used to mutate return values of Objective-C methods and functions</description><author>Juan Cruz Viotti</author><pubDate>Wed, 22 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.jviotti.com/2023/11/22/spoofing-objective-c-return-values-on-apple-silicon-using-lldb.html</guid></item><item><title>Betting on Deep Tech</title><link>https://www.codingvc.com/p/betting-on-deep-tech</link><description>Is deep tech a good area to build in and invest in? (TLDR: Yes!)</description><author>Coding VC</author><pubDate>Tue, 21 Nov 2023 15:36:29 GMT</pubDate><guid isPermaLink="true">https://www.codingvc.com/p/betting-on-deep-tech</guid></item><item><title>Testing ansible playbooks against multiple targets using vagrant</title><link>https://srijan.ch/testing-ansible-playbooks-using-vagrant</link><description>How to test your ansible playbooks against multiple target OSes and versions using Vagrant</description><author>Srijan Choudhary, all posts</author><pubDate>Tue, 21 Nov 2023 08:55:00 GMT</pubDate><guid isPermaLink="true">https://srijan.ch/testing-ansible-playbooks-using-vagrant</guid></item><item><title>What does vehicle congestion tracking mean for privacy?</title><link>https://utf9k.net/blog/vehicle-congestion-gnss/</link><description>Ironically, I don't actually drive currently so I'll be fine</description><author>utf9k</author><pubDate>Tue, 21 Nov 2023 07:00:00 GMT</pubDate><guid isPermaLink="true">https://utf9k.net/blog/vehicle-congestion-gnss/</guid></item><item><title>Introducing Colors</title><link>https://zacs.site/blog/introducing-colors.html</link><description>&lt;p&gt;Over a year ago I read Matthew Strom&amp;#8217;s article &lt;a href="https://matthewstrom.com/writing/how-to-pick-the-least-wrong-colors"&gt;&lt;em&gt;How to pick the least wrong colors&lt;/em&gt;&lt;/a&gt;, where he explored the fascinating intricacies of designing good color schemes. Here, &amp;#8220;good&amp;#8221; meant not just whether the colors looked nice, but also how distinguishable they were from each other and whether or not they accomodated individuals with conditions like protanopia, for example, who cannot see red light. He came up with a great solution and posted the NodeJS code &lt;a href="https://github.com/ilikescience/category-colors"&gt;to GitHub&lt;/a&gt;. Since I do most of my work in Python, though, I had few chances to take advantage of his research&amp;#160;&amp;#8212;&amp;#160;until now. My latest project, &lt;a href="https://github.com/zacjszewczyk/colors"&gt;Colors&lt;/a&gt;, helps to generate large sets of attractive and accessible color schemes. The current version of this project is, for the most part, a direct NodeJS-to-Python port of Matthew&amp;#8217;s code from 2022 with a few minor changes, but I look forward to building on it and keeping pace with Matthew. &lt;a href="https://github.com/zacjszewczyk/colors"&gt;Colors&lt;/a&gt; is currently available as a Jupyter notebook and a regular Python script.&lt;/p&gt;
                &lt;p&gt;&lt;a href="https://zacs.site/blog/introducing-colors.html"&gt;Permalink.&lt;/a&gt;&lt;/p&gt;</description><author>Zac Szewczyk</author><pubDate>Tue, 21 Nov 2023 05:35:00 GMT</pubDate><guid isPermaLink="true">https://zacs.site/blog/introducing-colors.html</guid></item><item><title>The Sand Mandala</title><link>https://ben.bolte.cc/posts/2023-11-21-the-sand-mandala</link><description>A vignette about purpose.</description><author>Ben Bolte</author><pubDate>Tue, 21 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ben.bolte.cc/posts/2023-11-21-the-sand-mandala</guid></item><item><title>Wild Speculation Regarding the OpenAI Fiasco</title><link>https://tiltingatwindmills.dev/wild-speculation-regarding-the-openai-fiasco/</link><description>First, I feel I need to make one thing clear: I'm nobody and have no special inside information. I'm just a random terminally-online guy who…</description><author>Tilting at Windmills</author><pubDate>Tue, 21 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://tiltingatwindmills.dev/wild-speculation-regarding-the-openai-fiasco/</guid></item><item><title>Why Is OOP So Popular?</title><link>https://backendhance.com/blog/2023/why-is-oop-so-popular/</link><description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;p&gt;Warum entwickeln wir heute objektorientiert und nicht funktional? Wieso sind 7 von 10 der populärsten Programmiersprachen objektorientiert? Und warum ist keine (rein) funktionale Sprache darunter?&lt;/p&gt;
&lt;p&gt;&lt;img alt="image.png" src="https://backendhance.com/blog/2023/why-is-oop-so-popular/14ddc445-d5cf-4eee-94d5-37fbbfc4f5d2.png" /&gt;&lt;br /&gt;
&lt;em&gt;Quelle: &lt;a href="https://survey.stackoverflow.co/2023/" rel="noopener noreferrer" target="_blank"&gt;Stackoverflow Developer Survey 2023&lt;i class="bx bx-link-external"&gt;&lt;/i&gt;&lt;/a&gt;
&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Die Frage hat sich auch &lt;a href="https://www.youtube.com/watch?v=QyJZzq0v7Z4" rel="noopener noreferrer" target="_blank"&gt;Richard Feldman 2019&lt;i class="bx bx-link-external"&gt;&lt;/i&gt;&lt;/a&gt;
gestellt. Und die Hintergründe sind interessant.&lt;/p&gt;
&lt;p&gt;Natürlich - wie immer - es ist kompliziert.&lt;/p&gt;
&lt;p&gt;Aber einen Aspekt finde ich besonders interessant.&lt;/p&gt;
&lt;p&gt;Richard fragt sich, &lt;strong&gt;ob es an einzigartigen Features in OOP liegt&lt;/strong&gt;, die in anderen Paradigmen nicht vorhanden sind.&lt;/p&gt;</description><author>Backendhance</author><pubDate>Tue, 21 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/why-is-oop-so-popular/</guid></item><item><title>Visualizing a GroupBy (or a Bipartite Graph)</title><link>https://trigonaminima.github.io/2023/11/bipartite-viz/</link><description>Have you ever needed to present the output of a GroupBy or Pivot Table?</description><author>Playground</author><pubDate>Tue, 21 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://trigonaminima.github.io/2023/11/bipartite-viz/</guid></item><item><title>"Change Data Capture Breaks Encapsulation". Does it, though?</title><link>https://www.morling.dev/blog/change-data-capture-breaks-encapsulation-does-it-though/</link><description>&lt;div class="toc" id="toc"&gt;
&lt;div id="toctitle"&gt;Table of Contents&lt;/div&gt;
&lt;ul class="sectlevel1"&gt;
&lt;li&gt;&lt;a href="#_cdca_quick_primer"&gt;CDC—​A Quick Primer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_does_cdc_break_encapsulation"&gt;Does CDC Break Encapsulation?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_entering_data_contracts"&gt;Entering Data Contracts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_implementation_approaches_for_data_contracts"&gt;Implementation Approaches For Data Contracts&lt;/a&gt;
&lt;ul class="sectlevel2"&gt;
&lt;li&gt;&lt;a href="#_the_outbox_pattern"&gt;The Outbox Pattern&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_stream_processing"&gt;Stream Processing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#_streaming_data_contractsbeyond_the_basics"&gt;Streaming Data Contracts—​Beyond the Basics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_handling_schema_changes"&gt;Handling Schema Changes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_summary"&gt;Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;em&gt;This post originally appeared on the &lt;a href="https://www.decodable.co/blog/change-data-capture-breaks-encapsulation-does-it-though"&gt;Decodable blog&lt;/a&gt;. All rights reserved.&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Having worked on Debezium—​an open-source platform for Change Data Capture (CDC)—​for several years, one concern I’ve heard repeatedly is this: aren’t you breaking the encapsulation of your application when you expose change event feeds directly from your database?
After all, CDC exposes your internal persistent data model to the outside world, which may have unintended consequences, e.g.
in terms of data exposure but also when it comes to changes to the schema of your data, which may break downstream consumers.&lt;/p&gt;
&lt;/div&gt;</description><author>Gunnar Morling</author><pubDate>Tue, 21 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.morling.dev/blog/change-data-capture-breaks-encapsulation-does-it-though/</guid></item><item><title>Portable writing setup with Onyx Boox Palma</title><link>https://liza.io/portable-writing-setup-with-onyx-boox-palma/</link><description>&lt;p&gt;I&amp;rsquo;ve been using portable writing setups for a while. I started with an AlphaSmart Neo. This was great, but with the screen angle being fixed not as versatile as I had wanted. I wrote a novella on it (said novella would later be extended and become a #1 best-seller in Amazon&amp;rsquo;s Science Fiction Romance category!)&lt;/p&gt;</description><author>Liza Shulyayeva</author><pubDate>Tue, 21 Nov 2023 00:01:33 GMT</pubDate><guid isPermaLink="true">https://liza.io/portable-writing-setup-with-onyx-boox-palma/</guid></item><item><title>Consuming RabbitMQ Messages in Go</title><link>https://benbrougher.tech/posts/consuming-rabbitmq-events-in-go/</link><description>How to use golang to consume a RabbitMQ queue and correctly ack and reject</description><author>benbrougher.tech</author><pubDate>Mon, 20 Nov 2023 14:40:22 GMT</pubDate><guid isPermaLink="true">https://benbrougher.tech/posts/consuming-rabbitmq-events-in-go/</guid></item><item><title>French Republican Calendar Tools</title><link>https://lagomor.ph/projects/repubcalendar/</link><description>I believe that in all facets of life, anything taken for granted should be examined. For me, this includes how I think about and interface with time. Since beginning research into this topic a few years ago, the French Republican Calendar has continued to fascinate and delight me, so much so to the point that I use decimal time and the republican calendar as my default method of tracking time in my personal life.</description><author>Home on Lagomorph</author><pubDate>Mon, 20 Nov 2023 09:00:00 GMT</pubDate><guid isPermaLink="true">https://lagomor.ph/projects/repubcalendar/</guid></item><item><title>2023-11-20-001</title><link>https://srijan.ch/notes/2023-11-20-001</link><description>@rogerlipscombe@hachyderm.io has a nice post on using git with multiple identities. His recommended way (using includeIf to include different config files for different parent folders) also makes sense to me the most.</description><author>Srijan Choudhary, all posts</author><pubDate>Mon, 20 Nov 2023 08:45:00 GMT</pubDate><guid isPermaLink="true">https://srijan.ch/notes/2023-11-20-001</guid></item><item><title>Open source, locally hosted AI powered Siri replacement</title><link>https://smcleod.net/2023/11/open-source-locally-hosted-ai-powered-siri-replacement/</link><description>Using an ESP32 S3 Box 3 with Willow, Home Assistant and Large Language Models (LLMs) create a locally hosted, offline, AI powered Siri / Alexa replacement.</description><author>smcleod.net</author><pubDate>Mon, 20 Nov 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2023/11/open-source-locally-hosted-ai-powered-siri-replacement/</guid></item><item><title>Art of Agile Development in Korean</title><link>https://www.jamesshore.com/v2/books/aoad2/translations/korean</link><description>&lt;div class="figure"&gt;
	&lt;a href="https://www.aladin.co.kr/m/mproduct.aspx?itemid=318446714"&gt;&lt;img alt="Book cover for the Korean translation of “The Art of Agile Development, Second Edition” by James Shore. The title reads, “[국내도서] 애자일 개발의 기술 2/e”. It’s translated by 김모세 and published by O’Reilly. Other than translated text, the cover is the same as the English edition, showing a water glass containing a goldfish and a small sapling with green leaves." class="framed" height="375" src="https://www.jamesshore.com/images/aoad2/cover/aoad2-korean-cover-959x1200.jpg" width="300" /&gt;&lt;/a&gt;
&lt;/div&gt;

&lt;p&gt;I’m pleased to announce that the &lt;strong&gt;Korean translation&lt;/strong&gt; of &lt;em&gt;&lt;a href="https://www.jamesshore.com/v2/books/aoad2"&gt;The Art of Agile Development&lt;/a&gt;&lt;/em&gt; is now available! You can &lt;a href="https://www.aladin.co.kr/m/mproduct.aspx?itemid=318446714"&gt;buy it here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Many thanks to 김모세 for their hard work on this translation.&lt;/p&gt;</description><author>James Shore</author><pubDate>Mon, 20 Nov 2023 02:39:01 GMT</pubDate><guid isPermaLink="true">https://www.jamesshore.com/v2/books/aoad2/translations/korean</guid></item><item><title>USearch Molecules: 28 Billion Chemical Embeddings on AWS ⚗️</title><link>https://ashvardanian.com/posts/usearch-molecules/</link><description>Indexing 28 billion chemical embeddings from 7 billion molecules using optimized Jaccard distance kernels, achieving 99% recall at 3,700 QPS on AWS Open Data.</description><author>Ash's Blog</author><pubDate>Mon, 20 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ashvardanian.com/posts/usearch-molecules/</guid></item><item><title>"y'all'n't"</title><link>https://taylor.town/yallnt</link><description>Y'all'n't sophisticated.</description><author>taylor.town</author><pubDate>Mon, 20 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/yallnt</guid></item><item><title>Exploring macOS private frameworks</title><link>https://www.jviotti.com/2023/11/20/exploring-macos-private-frameworks.html</link><description>This article describes a series of approaches for reverse engineering macOS private frameworks</description><author>Juan Cruz Viotti</author><pubDate>Mon, 20 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.jviotti.com/2023/11/20/exploring-macos-private-frameworks.html</guid></item><item><title>What I miss about pandemic life</title><link>https://callmeo.live/blog/what-i-miss-about-pandemic-life/</link><description>&lt;p&gt;With the WHO declaring that &lt;a href="https://news.un.org/en/story/2023/05/1136367"&gt;Covid is no longer a global emergency&lt;/a&gt;, we&amp;rsquo;ve quickly been pushed back to life as it was before the pandemic, with barely a thought given to what things were like for a few years.&lt;/p&gt;
&lt;p&gt;Life was in no way a fun jaunty romp, but when I look back I find myself amazed at how we acted back then – and how in some ways we&amp;rsquo;ve went back.&lt;/p&gt;
&lt;h1 id="1-we-did-away-with-handshakes-for-a-bit"&gt;1) We did away with handshakes for a bit&lt;/h1&gt;
&lt;p&gt;Does anyone &lt;em&gt;really&lt;/em&gt; like handshakes? The awkward dance of meeting eyes, guiding hands without vision with no clue as to how firmly to grasp (and the uncomfortable moment when you squeeze down on the limpest hand known to man). The shaking of hands staining your palm with thick layers of oil and grease and leaving you waiting until it&amp;rsquo;s socially acceptable to run to the nearest sink to scrub your hands clean&amp;hellip; I can&amp;rsquo;t be doing with it.&lt;/p&gt;
&lt;p&gt;With the pandemic came the novel introduction of the &lt;a href="https://en.wikipedia.org/wiki/Elbow_bump"&gt;Elbow bump&lt;/a&gt;, the bumbling alternative that turned you to a drunken duellist, slowly leaning in with a cumbersome thrust. Whilst more time-consuming, it eliminated the problem of &lt;em&gt;oh god I hate touching people&amp;rsquo;s hands&lt;/em&gt;, and the embarrassment of touching elbows was mutual.&lt;/p&gt;
&lt;p&gt;For me, the gold standard remains the Fist bump. Quick, efficient, minimal. Keeps respect while acting casual. Perfect.&lt;/p&gt;
&lt;h1 id="2-going-the-distance-all-of-2-metres"&gt;2) Going the distance (all of 2 metres)&lt;/h1&gt;
&lt;p&gt;Last week while shopping I noticed something odd by my local&amp;rsquo;s checkouts – ancient markings 2 metres apart, faded relics from when Covid run rabid guiding us to where to stand. My trance was broken as the bloke behind me coughed on the back of my head. Bit rude. No need to squeeze in so tight mate.&lt;/p&gt;
&lt;p&gt;Health aside, the fact that you didn&amp;rsquo;t have to defend your personal space from some folk was a godsend.&lt;/p&gt;
&lt;h1 id="3-the-cleansing-of-the-ads"&gt;3) The Cleansing of the Ads&lt;/h1&gt;
&lt;p&gt;For whatever reason, the first year and a half of the pandemic killed all physical advertising in my area, and it was blissful. No longer was I assaulted with a kaleidoscope of jarring colours and bright shapes telling me how my life was shit because I haven&amp;rsquo;t bought an assortment of products and services – and it&amp;rsquo;s funny how hunger wasn&amp;rsquo;t an issue on my commute when massive displays of food suddenly disappeared.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d do anything to have that back. Life without advertising is so much calmer on my eyes, and as garish displays returned to plaster every square inch of space I&amp;rsquo;ve found myself frustrated at how they pry my attention away from the world. We could all do without such visual pollution.&lt;/p&gt;
&lt;h1 id="4-less-litter-littler-litter-words"&gt;4) Less litter. Littler litter. Words.&lt;/h1&gt;
&lt;p&gt;There&amp;rsquo;s nothing as lush as the British outdoors, a grand canvas adorned with every shade of green, adored.&lt;/p&gt;
&lt;p&gt;Unfortunately wherever you go there&amp;rsquo;s a mobs of littering gits who&amp;rsquo;ll find any excuse to fill hedgerows with whatever they can&amp;rsquo;t be arsed to put in their pockets. With less of them going out and spoiling the outside world, the state of Britain&amp;rsquo;s nature pathways were a sight to behold. Who let them know the pandemic was over?&lt;/p&gt;
&lt;h1 id="5-all-in-it-together"&gt;5) All in it together*&lt;/h1&gt;
&lt;p&gt;Stuck in our fast &amp;amp; fractured worlds, we all run about our daily lives isolated from each other&amp;rsquo;s struggles. Covid swiftly got in the way of that, and as we were put under lockdown there quickly became one unifying truth about the pandemic: life is shit now, or at least more than usual.&lt;/p&gt;
&lt;p&gt;There was no avoiding it, and when the &lt;a href="https://www.bbc.co.uk/news/business-52040532"&gt;bog roll battle royale&lt;/a&gt; simmered down the state of life eventually brought people closer together (safely within 2m of each other). Empathy rode up to new heights as we got past the initial waves of stress, and no longer did people judge one another for the states of their lives or for being behind. You could be living in a tip and it didn&amp;rsquo;t matter, everyone else might as well have been too.&lt;/p&gt;
&lt;p&gt;Unfortunately, &lt;em&gt;not everyone was in it together&lt;/em&gt;. But unlike day-to-day life where we&amp;rsquo;re apathetic towards those in powers flaunting the rules and too burnt out to be outraged, when we found out that &lt;a href="https://www.itv.com/news/2022-05-23/exclusive-pm-pictured-drinking-at-downing-street-party-during-lockdown"&gt;prominent politicians&lt;/a&gt; were &lt;a href="https://www.theguardian.com/politics/2021/jun/25/matt-hancock-gina-coladangelo-grant-shapps-health-job"&gt;breaking rules&lt;/a&gt; (that they themselves had drawn up!), we were rightfully furious. Why should we sacrifice this much if you can&amp;rsquo;t show the most basic respect for us?&lt;/p&gt;
&lt;p&gt;The fury could be heard from the heavens as story after story leaked out to the press, and such cases kicked off the downward spiral of multiple political careers – thinking about those figures sweating like pigs in front of an angry public is still cathartic, but we must be careful. As we&amp;rsquo;ve returned to normal we&amp;rsquo;ve slipped back into general apathy at the state of things; and worse, we&amp;rsquo;ve became complacent at a media industry gifting such disgraced figures free PR via &lt;a href="https://www.bbc.co.uk/news/uk-politics-64464928"&gt;juicy reality TV gigs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But politicians aside, I&amp;rsquo;m thankful that the widespread acceptance of mental health issues has managed to stick around.&lt;/p&gt;
&lt;h1 id="6-washing-hands-holy-moly"&gt;6) washing hands holy moly&lt;/h1&gt;
&lt;p&gt;genuine question: how do you forget about washing your hands after doing it for two years straight??? Mental.&lt;/p&gt;</description><author>callmeolive</author><pubDate>Sun, 19 Nov 2023 21:10:00 GMT</pubDate><guid isPermaLink="true">https://callmeo.live/blog/what-i-miss-about-pandemic-life/</guid></item><item><title>Sharing is caring: in defense of ordinary things</title><link>https://www.georgeyw.com/sharing-is-caring/</link><description>On foggy mornings and small talk</description><author>This Is My True Name</author><pubDate>Sun, 19 Nov 2023 19:53:11 GMT</pubDate><guid isPermaLink="true">https://www.georgeyw.com/sharing-is-caring/</guid></item><item><title>Design The Data Class Model Instead Of Coding It</title><link>https://fredgrott.medium.com/design-the-data-class-model-instead-of-coding-it-b1163e3b5d3f?source=rss-2cddef1829fb------2</link><description>&lt;div class="medium-feed-item"&gt;&lt;p class="medium-feed-image"&gt;&lt;a href="https://fredgrott.medium.com/design-the-data-class-model-instead-of-coding-it-b1163e3b5d3f?source=rss-2cddef1829fb------2"&gt;&lt;img src="https://cdn-images-1.medium.com/max/1080/1*hh0fzGJa4gyn9gg7xlqqtA.png" width="1080" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p class="medium-feed-snippet"&gt;Re-align that data class model for the UX driven flutter mobile app design process.&lt;/p&gt;&lt;p class="medium-feed-link"&gt;&lt;a href="https://fredgrott.medium.com/design-the-data-class-model-instead-of-coding-it-b1163e3b5d3f?source=rss-2cddef1829fb------2"&gt;Continue reading on Medium »&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;</description><author>Stories by Fred Grott on Medium</author><pubDate>Sun, 19 Nov 2023 14:55:42 GMT</pubDate><guid isPermaLink="true">https://fredgrott.medium.com/design-the-data-class-model-instead-of-coding-it-b1163e3b5d3f?source=rss-2cddef1829fb------2</guid></item><item><title>Choosing an encrypted DNS protocol</title><link>https://seirdy.one/notes/2023/11/18/choosing-an-encrypted-dns-proto/</link><description>&lt;p&gt;The three most popular DNS protocols with transit encryption are DNS-over-HTTPS (&lt;abbr&gt;DoH&lt;/abbr&gt;), DNS-over-TLS (&lt;abbr&gt;DoT&lt;/abbr&gt;), and DNS-over-QUIC (&lt;abbr&gt;DoQ&lt;/abbr&gt;). This should help you choose what to use:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Do you actually need to override OS DNS support? If not, or if you’re unsure, go to 6.&lt;/li&gt;&lt;li&gt;Are you ready to implement DNS protocols correctly, or add a dependency that does so? If you’re not, go to 5.&lt;/li&gt;&lt;li&gt;Does the network filter DNS traffic? If it does, go to 5.&lt;/li&gt;&lt;li&gt;Do you already have QUIC support? If not, &lt;strong&gt;use DoT.&lt;/strong&gt; If you do, &lt;strong&gt;use DoQ.&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;Do you have an HTTPS stack? If you do, &lt;strong&gt;use DoH&lt;/strong&gt;.&lt;/li&gt;&lt;li&gt;Give up and &lt;strong&gt;delegate to the OS&lt;/strong&gt;.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Let your HTTPS stack handle HTTP/1.1 vs. HTTP/2 vs. HTTP/3 support; don’t treat DNS-over-HTTP/3 as a separate protocol. I don’t know enough about DNSCrypt to make an informed recommendation about it, but DoQ and DoH meet my needs well enough.&lt;/p&gt;</description><author>All content on Seirdy’s Home</author><pubDate>Sun, 19 Nov 2023 05:33:09 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/notes/2023/11/18/choosing-an-encrypted-dns-proto/</guid></item><item><title>Building a digital vigil for those we've lost</title><link>https://ntietz.com/blog/digital-vigil-for-tdor/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;This post is hard to write in a lot of ways.
It's more personal than most I've written.
This is presumptively a tech blog, and this piece is about so much more than technology.
But it's important.&lt;/p&gt;
&lt;p&gt;Making things, software or otherwise, is ultimately about &lt;em&gt;people&lt;/em&gt;.
One of the ways I express love for the people I care about is through making things.
Whether that's a hot meal, a picture frame, or a piece of software, it's &lt;em&gt;people&lt;/em&gt; who make making matter.&lt;/p&gt;
&lt;p&gt;And so I made a &lt;a href="https://tdor.xyz"&gt;Digital Vigil for Transgender Day of Remembrance&lt;/a&gt;.
The rest of this post explains why and how.
I hope you'll join me in this.&lt;/p&gt;
&lt;h1 id="tdor-and-what-it-means-to-me"&gt;TDoR and what it means to me&lt;/h1&gt;
&lt;p&gt;Tomorrow is &lt;a href="https://en.wikipedia.org/wiki/Transgender_Day_of_Remembrance"&gt;Transgender Day of Remembrance&lt;/a&gt; (or TDoR).
It's a day to memorialize those who have died at the hands of transphobia.
Some were murdered, some were lost to suicide.&lt;/p&gt;
&lt;p&gt;This day has not been something that I was very conscious of in the past.
It was relatively easy to stay detached.
This year, that's a lot harder, for two reasons.&lt;/p&gt;
&lt;p&gt;The first reason is that someone I know is on the list this time.
She was a big influence on me during my internship.
We worked on different teams, but she was &lt;em&gt;the&lt;/em&gt; epitome of the engineer I wanted to become: she was the badass engineer that you could always turn to, and it felt like she would know the solution to your problem from half a sentence.
She was the first engineer I'd worked with who I saw transition, from afar.
I saw her continue her successful career, remaining respected by coworkers and thriving during and after her transition.
Thank you to the former coworker who let me know about her passing; you mean the world to me, too, and I know her loss hit you hard.&lt;/p&gt;
&lt;p&gt;The second reason is that I now hold a fear of someday ending up on this list myself.
I'm a trans woman, and the world is not the friendliest to us.
My lot in life is still relatively nice.
I live in a small accepting town; my parents in another; my employer and coworkers have been grand; and the programming community I'm in is a warm hug.
But attacks on all trans people are on the rise, and our access to medical care is at risk with the next general election.
If I'm forced to medically detransition, I don't know how my mental health could survive intact.
If we're all forced to medically detransition, the list of those we've lost will be much longer.&lt;/p&gt;
&lt;p&gt;So this year I learned more about the day.
I looked at the &lt;a href="https://tdor.translivesmatter.info/reports"&gt;list of names&lt;/a&gt; that would be read this year, and I cried.
And then I started looking to participate.
There isn't a candlelight vigil that I can attend in person this year, because I will be traveling for Thanksgiving.&lt;/p&gt;
&lt;p&gt;Since I cannot attend a vigil in person, I thought about other ways that I could participate.
I toyed with the idea of doing something with software (because &lt;em&gt;of course I did&lt;/em&gt;), and wasn't sure what I wanted to do.
It crystallized this week after my therapy session, though.&lt;/p&gt;
&lt;h1 id="making-my-own-vigil"&gt;Making my own vigil&lt;/h1&gt;
&lt;p&gt;It is deeply important to me to participate in TDoR, and making things is my most expressive way of showing love and care.
Making something for TDoR would be a way to put my heart into the day, and a way to build the experience I cannot get in person.
Plus, it is something that is very visual and user-focused, so a good way to stretch a little outside of my usual areas of focus.&lt;/p&gt;
&lt;p&gt;So I started with a simple concept: give people the ability to read the list of names one by one, and experience lighting a candle for each name.
Ideally we would add some nice styling, and make the candle look a little animated, but it would depend on time and getting help—I'm not very skilled at CSS.&lt;/p&gt;
&lt;p&gt;This is a nice use-case for a frontend application.
React was the obvious choice, but configuring React gives me a headache&lt;sup class="footnote-reference" id="fr-1-1"&gt;&lt;a href="https://ntietz.com/blog/digital-vigil-for-tdor/#fn-1"&gt;[1]&lt;/a&gt;&lt;/sup&gt; and it's been a while, so there was a lot of friction to starting.
On a whim, I tried &lt;a href="https://yew.rs/"&gt;Yew&lt;/a&gt; instead.
Yew is&lt;sup class="footnote-reference" id="fr-2-1"&gt;&lt;a href="https://ntietz.com/blog/digital-vigil-for-tdor/#fn-2"&gt;[2]&lt;/a&gt;&lt;/sup&gt; a framework for writing single-page apps using Rust and WASM.
For such a simple application as this one, it was quite easy to get started.&lt;/p&gt;
&lt;p&gt;The starter example, a counter that you click to increment, was the foundation of the application.
We have a list of names, and each time we press a button it will advance through the list of names.
What is this except a counter pointing to the index of which name is next to read?
So I took the starter example and connected it up to a list of names, which will then render each one on the page.&lt;/p&gt;
&lt;p&gt;After that it was a lot of polishing and tweaking the layout and the look.
The primary task that intimidated me was making a candle in HTML/CSS, but we took the easy route and found a &lt;a href="https://codepen.io/chendf/pen/wvGWoEm"&gt;permissively-licensed example&lt;/a&gt; and adapted it.
Trim out the taller candle, then scale the whole thing to the size we want, and we've got it.
With some basic styling to make the candle sit with each name in a list, this was the core of the application.&lt;/p&gt;
&lt;p&gt;The remaining work was to make it functional as more than a cute demo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your state is saved in &lt;code&gt;localStorage&lt;/code&gt;, so if you refresh the page you can start reading from where you left off&lt;/li&gt;
&lt;li&gt;There are multiple views for the introduction text, the credits, and the vigil itself&lt;/li&gt;
&lt;li&gt;You can reset the state if you want to start over&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There were a lot of little CSS tweaks, too.
The most recent one was making the candles fade in.
I'm sure there is more that can be done here, but this is emotional work for me.&lt;/p&gt;
&lt;p&gt;Huge thanks to &lt;a href="https://erikarow.land/"&gt;Erika&lt;/a&gt; and an anonymous Recurser who helped me with creating the candles, the layout, the application logic... really with all of it.
It is much easier to complete an emotionally difficult project when you have friends to help you along the way ❤️.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://sr.ht/~ntietz/tdor-digital-vigil/"&gt;code is open-source&lt;/a&gt;, and contributions are welcome if you think of ways to improve it.
One thing that I think would be nice is to add some more information with each name.
This data is available, but I did not have the emotional bandwidth to work through adding it into the application.&lt;/p&gt;
&lt;h1 id="please-read-their-names"&gt;Please read their names&lt;/h1&gt;
&lt;p&gt;Tomorrow is Transgender Day of Remembrance.
Please join me in memorializing the day by participating in a vigil.
This may be in person, or it may be by using the &lt;a href="https://tdor.xyz"&gt;digital vigil&lt;/a&gt; that I built.&lt;/p&gt;
&lt;p&gt;This is a very important time to keep all these people in your hearts, to hold them in the light.
We are heading into Thanksgiving, and many will be gathering to share meals.
Some seats will be empty.&lt;/p&gt;
&lt;p&gt;Let's come together to remember those we've lost, and then work to make sure we don't lose more in the future.&lt;/p&gt;
&lt;p&gt;And Catherine? You rock. I wish I'd gotten to tell you that.&lt;/p&gt;
&lt;hr /&gt;
&lt;hr /&gt;&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-1"&gt;
&lt;p&gt;JavaScript and TypeScript also do not spark joy for me, so if I can avoid them that would be nice. &lt;a href="https://ntietz.com/blog/digital-vigil-for-tdor/#fr-1-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-2"&gt;
&lt;p&gt;My brain unhelpfully chimes in "no, you &lt;em&gt;are&lt;/em&gt;." &lt;a href="https://ntietz.com/blog/digital-vigil-for-tdor/#fr-2-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Sun, 19 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/digital-vigil-for-tdor/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Knuth Airgaps &amp;amp; Knuth Buffers</title><link>https://taylor.town/knuth-airgap</link><description>Knuth knows how to maintain information hygeine.</description><author>taylor.town</author><pubDate>Sun, 19 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/knuth-airgap</guid></item><item><title>Social media and social life</title><link>https://ellie.wtf/posts/social-media-and-social-life/</link><description>&lt;p&gt;Over the past year or so, I've been scaling back how much time and energy I put into social media.&lt;/p&gt;
&lt;p&gt;For a long time, I'd keep my Instagram story "up to date", regularly post on TikTok, and spend far too much of my day scrolling various feeds. Lately, I've been scaling that back. I know a number of people who've totally deleted their accounts, but for me that wasn't quite desirable. I didn't want to risk losing touch with people or what's going on. Much as I don't like it, a bunch of social organising has moved to social media.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A number of geographically distant friends I keep in touch with almost exclusively over Instagram&lt;/li&gt;
&lt;li&gt;I've found tech community on Twitter + Mastodon&lt;/li&gt;
&lt;li&gt;It's useful for meeting new people while travelling&lt;/li&gt;
&lt;li&gt;It can be useful for work&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Lots of my behaviours towards the consumption of social media were not what I regarded to be healthy. Much as it can be a useful tool, it is a tool that requires control; otherwise, it will use you.&lt;/p&gt;
&lt;h1 id="applying-limits"&gt;Applying limits&lt;/h1&gt;
&lt;p&gt;I started off trying to limit things. I figured I could retain some of what I saw as upsides while reducing the downsides if I stuck to say, 15-30 mins a day.&lt;/p&gt;
&lt;h2 id="attempt-1-screen-time-limits"&gt;Attempt #1: Screen time limits&lt;/h2&gt;
&lt;p&gt;I initially setup Apple screen time limits. These did a pretty good job of stopping me from scrolling, for a few days anyway.&lt;/p&gt;
&lt;p&gt;After 30 minutes, the screen time limit pops up. Unfortunately, it's easy to bypass - my poor impulse control and big-tech addiction soon got used to dismissing this without even noticing.&lt;/p&gt;
&lt;h2 id="attempt-2-one-sec"&gt;Attempt #2: One sec&lt;/h2&gt;
&lt;p&gt;Next up, I tried &lt;a href="https://one-sec.app/" rel="external"&gt;One Sec&lt;/a&gt;. It essentially delays your access to apps, making you wait for a certain number of seconds before it is possible to open them. This actually worked pretty well! It interrupted the easy-dopamine reach of just opening social media for "a few seconds", and forced me to actually think about what I was doing. Most of the time, it resulted in me remembering that I wanted to spend my life differently and backing out.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://img.ellie.wtf/i/4fdb295d3303f0871d00782a3ef40157329f52c4f9698241c1cc2bb4ed99af56.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;After a while, I noticed a different pattern. Sometimes I'd open the app before I anticipated I'd actually want to use it, and other times I'd just use it for longer. My overall usage had gone down a whole bunch though, so I'd call this a partial success.&lt;/p&gt;
&lt;h1 id="just-delete-it"&gt;Just delete it&lt;/h1&gt;
&lt;p&gt;The main thing holding me back from uninstalling Instagram and Twitter was the messengers. Especially with international friends, it's easier to swap social media than it is to request phone numbers - and often more socially acceptable. Even though I've put a great deal of effort into trying to move people to better alternatives, it's realistically not going to happen any time soon.&lt;/p&gt;
&lt;p&gt;If I tried going on a techie rant about how addicting social media is, how it's used to control groups of people, and how "if it's free, you're the product"... most of my not-tech friends would ignore me and think I was losing it. I admire those who stick so strongly to their principals there, but I'm not willing to pay the social cost.&lt;/p&gt;
&lt;p&gt;Recently I setup &lt;a href="https://www.beeper.com/" rel="external"&gt;Beeper&lt;/a&gt;. It has done an amazing job of totally replacing social media apps for messaging. I can still keep in touch with people where I need to, but I don't end up getting sucked into a time vacuum. Fantastic!&lt;/p&gt;
&lt;h2 id="tiktok"&gt;TikTok&lt;/h2&gt;
&lt;p&gt;The first thing I removed was TikTok. It had an uncanny ability to absolutely entrap my attention, for an embarrassingly long period of time. After an hours doom-scrolling, there was rarely (if ever) anything I could remember. The app is excellent at absorbing your time and contributing little to your life. The worst part was that most of the posts weren't anything at all from my real friends, just internet personalities and memes.&lt;/p&gt;
&lt;p&gt;For a while, I actually used to post videos - mostly relating to motorcycles. A few of these ended up with millions of views. TikTok analytics show the total amount of "human time" that has been spent, per video. It showed that videos I had uploaded, putting in very little effort, had been complicit in pointlessly absorbing &lt;em&gt;days&lt;/em&gt; of combined human lifespan. Across the platform, it must be measured in decades/centuries.&lt;/p&gt;
&lt;p&gt;Some people have said they manage to learn useful things from TikTok, but that wasn't really my experience. I'm also unsure if people convince themselves of this, so that they feel less bad about spending so much time on it.&lt;/p&gt;
&lt;h2 id="instagram"&gt;Instagram&lt;/h2&gt;
&lt;p&gt;After a while, I uninstalled the Instagram app entirely. I didn't delete my account, as I do still find it useful from time-to-time. I started using Instagram purely through the instagram.com web app, and it hasn't been anywhere near as addictive.&lt;/p&gt;
&lt;p&gt;I think the lack of polish on mobile (vs the apps) is just about jarring enough that I don't waste any time at all! I've actually found it interesting how the UI being "smooth" keeps me absorbed, but if it's jerkier/less native the likelihood of me becoming too absorbed decreases dramatically. It's usually the case that after a couple of minutes I just think "wow, there really isn't a lot here huh" and close the page.&lt;/p&gt;
&lt;p&gt;I mentioned above, but &lt;a href="https://www.beeper.com/" rel="external"&gt;Beeper&lt;/a&gt; has really helped here. I can continue to message people who prefer staying in touch via Instagram, without actually having it installed on my phone.&lt;/p&gt;
&lt;h2 id="twitter-x"&gt;Twitter / X&lt;/h2&gt;
&lt;p&gt;I've kept Twitter for now. Much as I'm not a big fan of the platform, I still maintain connections with a bunch of people there, and it has been useful for my work. I'd like to see that change, but for now it is still the case.&lt;/p&gt;
&lt;p&gt;The app is still on my phone, but I think soon I'll be banishing it to a Firefox tab just like Instagram.&lt;/p&gt;
&lt;h2 id="facebook"&gt;Facebook&lt;/h2&gt;
&lt;p&gt;I honestly haven't used this in several years. I have an account, but it's inactive. Nothing to remove!&lt;/p&gt;
&lt;h2 id="mastodon"&gt;Mastodon&lt;/h2&gt;
&lt;p&gt;I've found Mastodon to be very refreshing. The total lack of algorithm means I can very quickly have a quick read of what's been shared since my last visit, and then get back to my day. There's not new content every time the spinner loads.&lt;/p&gt;
&lt;p&gt;Generally (and perhaps that's not everyone's experience) I've found the community there to be much more pleasant. Less flamewars, less politics, more genuine humanity.&lt;/p&gt;
&lt;p&gt;I'll be keeping this one 😊&lt;/p&gt;
&lt;h1 id="relaxing"&gt;Relaxing&lt;/h1&gt;
&lt;p&gt;I've heard lots of people say that they like checking social media as a way to wind down, or to relax. Obviously I'm unable to speak for everyone, but for me personally there's a huge difference between conscious and intentional winding down, and switching your brain off while the magic box feeds you with whatever the algorithm deems best for engagement. One is restful, while the other burns time.&lt;/p&gt;
&lt;h1 id="conclusion"&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;After a few months, I've found that I have much more time. I've filled it with other things - more time outdoors, reading books much more often, and more time for projects. I'm also trying to learn the guitar.&lt;/p&gt;
&lt;p&gt;So far, I've been writing much more too! There's a whole bunch of drafts for my blog, and notes are being published pretty regularly.&lt;/p&gt;
&lt;p&gt;I no longer reach for my phone at the first sign of boredom or "empty time", instead using it purely as a tool. I think there's still work to do here, but it's a big improvement.&lt;/p&gt;
&lt;p&gt;In an ideal world, tech wouldn't have such a grip on our lives. But there's certainly compromises we can make, without having to go all out and delete everything.&lt;/p&gt;</description><author>Ellie's Notes</author><pubDate>Sun, 19 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ellie.wtf/posts/social-media-and-social-life/</guid></item><item><title>Exploring a Postgres query plan</title><link>http://notes.eatonphil.com/2023-11-19-exploring-a-postgres-query-plan.html</link><description>&lt;p&gt;&lt;!-- -*- mode: markdown -*- --&gt;&lt;/p&gt;
&lt;p&gt;I learned this week that you can intercept and redirect Postgres query
execution. You can hook into the execution layer so you're given a
query plan and you get to decide what to do with it. What rows to
return, if any, and where they come from.&lt;/p&gt;
&lt;p&gt;That's very interesting. So I started writing code to explore execution
hooks. However, I got stuck interpreting the query plan. Either
there's no query plan walking infrastructure or I just didn't find it.&lt;/p&gt;
&lt;p&gt;So this post is a digression into walking a Postgres query plan. By
the end we'll be able to run &lt;code&gt;psql -c 'SELECT a FROM x WHERE a &amp;gt; 1'&lt;/code&gt;
and reconstruct the entire SQL string from a Postgres
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/executor/execdesc.h#L33"&gt;&lt;code&gt;QueryDesc&lt;/code&gt;&lt;/a&gt;
object, the query plan object Postgres builds.&lt;/p&gt;
&lt;p&gt;With that query plan walking infrastructure in place, we'll be in a
good state to not just print out the query plan while walking it but
instead to translate the query plan or evaluate it in our own way
(e.g. over column-wise data, or &lt;a href="https://github.com/citusdata/postgres_vectorization_test"&gt;vectorized execution over row-wise
data&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Code for this project is &lt;a href="https://github.com/eatonphil/pgexec"&gt;available on
Github&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="what-is-a-query-plan?"&gt;What is a query plan?&lt;/h3&gt;&lt;p&gt;If you're familiar with parsers and compilers, a query plan is like an
intermediate representation (IR) of a program. It is not as raw as an
abstract syntax tree (AST); it has already been optimized.&lt;/p&gt;
&lt;p&gt;If that doesn't mean anything to you, think of a query plan as a
structured and optimized version of the SQL query you submit to your
database. It isn't text anymore. It is &lt;a href="https://buttondown.email/jaffray/archive/why-are-query-plans-trees/"&gt;probably a
tree&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Check out another Justin Jaffray &lt;a href="https://justinjaffray.com/what-is-a-query-optimizer-for/"&gt;article on the
subject&lt;/a&gt; for
more detail.&lt;/p&gt;
&lt;h3 id="development-environment"&gt;Development environment&lt;/h3&gt;&lt;p&gt;Before we get to walking the query plan, let's set up the
infrastructure to intercept query execution where we can eventually
add in our print debugging of the query plan reconstructed as a SQL
string.&lt;/p&gt;
&lt;p&gt;Once you've got &lt;a href="https://wiki.postgresql.org/wiki/Compile_and_Install_from_source_code"&gt;Postgres build
dependencies&lt;/a&gt;,
build and install a debug version of Postgres:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/postgres/postgres&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;postgres
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# Make sure you're on the same commit I'm on, just to be safe.&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;checkout&lt;span class="w"&gt; &lt;/span&gt;b218fbb7a35fcf31539bfad12732038fe082a2eb
$&lt;span class="w"&gt; &lt;/span&gt;./configure&lt;span class="w"&gt; &lt;/span&gt;--enable-cassert&lt;span class="w"&gt; &lt;/span&gt;--enable-debug&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;CFLAGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-ggdb -Og -g3 -fno-omit-frame-pointer&amp;quot;&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;-j8
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# Installs to to /usr/local/pgsql/bin.&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I'm not going to cover Postgres extension infrastructure in detail. I
wrote a bit about it in &lt;a href="https://notes.eatonphil.com/2023-11-01-postgres-table-access-methods.html"&gt;my last
post&lt;/a&gt;.
You need only read the first half, if at all; not the actual Table
Access Method implementation.&lt;/p&gt;
&lt;p&gt;It will be even simpler in this post because Postgres hooks are
extensions but not extensions you install with &lt;code&gt;CREATE EXTENSION&lt;/code&gt;. If
you want to read about the different kinds of Postgres extensions,
check out &lt;a href="https://tembo.io/blog/four-types-of-extensions/"&gt;this
article&lt;/a&gt; by Steven
Miller.&lt;/p&gt;
&lt;p&gt;The minimum we need, aside from the hook code itself, is a Makefile
that uses
&lt;a href="https://www.postgresql.org/docs/current/extend-pgxs.html"&gt;PGXS&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;MODULES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pgexec

&lt;span class="nv"&gt;PG_CONFIG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/usr/local/pgsql/bin/pg_config
&lt;span class="nv"&gt;PGXS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;shell&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;PG_CONFIG&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--pgxs&lt;span class="k"&gt;)&lt;/span&gt;
&lt;span class="cp"&gt;include $(PGXS)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;MODULES&lt;/code&gt; value there corresponds to the C file we'll create
shortly, &lt;code&gt;pgexec.c&lt;/code&gt;.&lt;/p&gt;
&lt;p class="note"&gt;
  This &lt;code&gt;pg_config&lt;/code&gt; binary path is important because you
  might have different versions of Postgres installed, for example by
  your package manager. It is important that the extension is built
  against the same version of Postgres which will load the extension.
&lt;/p&gt;&lt;p&gt;Now we're ready for some hook code.&lt;/p&gt;
&lt;h3 id="intercepting-query-execution"&gt;Intercepting query execution&lt;/h3&gt;&lt;p&gt;You can find the basic structure of a hook (and which hooks are
available) in Tamika Nomara's &lt;a href="https://github.com/taminomara/psql-hooks"&gt;unofficial Postgres hooks
docs&lt;/a&gt;.&lt;/p&gt;
&lt;p class="note"&gt;
  There is no official central place describing all hooks I could find
  in Postgres docs. Some hooks are described in various places
  throughout the docs though.
&lt;/p&gt;&lt;p&gt;Based on that page, we can write a bare minimum hook that will
intercept queries, log when we've done so, and pass control back
to the standard execution path for the actual query. In &lt;code&gt;pgexec.c&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;postgres.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;fmgr.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;executor/executor.h&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;PG_MODULE_MAGIC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ExecutorRun_hook_type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prev_executor_run_hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;print_plan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QueryDesc&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;elog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[pgexec] HOOKED SUCCESSFULLY!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pgexec_run_hook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;QueryDesc&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ScanDirection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;uint64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;execute_once&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;print_plan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prev_executor_run_hook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;execute_once&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_PG_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;prev_executor_run_hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ExecutorRun_hook&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prev_executor_run_hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;prev_executor_run_hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;standard_ExecutorRun&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ExecutorRun_hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pgexec_run_hook&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_PG_fini&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ExecutorRun_hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prev_executor_run_hook&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can discover the &lt;code&gt;standard_ExectutorRun&lt;/code&gt; function from a quick
&lt;code&gt;git grep ExecutorRun_hook&lt;/code&gt; in the Postgres source which leads to
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/backend/executor/execMain.c#L306"&gt;src/backend/executor/execMain.c#L306&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="nf"&gt;ExecutorRun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QueryDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;ScanDirection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uint64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;execute_once&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ExecutorRun_hook&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ExecutorRun_hook&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;execute_once&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;standard_ExecutorRun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;execute_once&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So our hook will just log and pass back execution to the existing
execution hook. Let's build and install the extension.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;make
&lt;span class="gp"&gt;$ &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we'll create a new database and tell it to load the extension.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/initdb&lt;span class="w"&gt; &lt;/span&gt;test-db
&lt;span class="gp"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;shared_preload_libraries = 'pgexec'&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;test-db/postgresql.conf
&lt;/pre&gt;&lt;/div&gt;
&lt;p class="note"&gt;
  Remember, hooks are not &lt;code&gt;CREATE EXTENSION&lt;/code&gt; extensions. As
  far as I can tell they can't be dynamically loaded (without some
  additional dynamic loading infrastructure one could potentially
  write). So every time you make a change you need to rebuild the
  extension, reinstall it, and restart the Postgres server.
&lt;/p&gt;&lt;p&gt;And start the server in the foreground:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/postgres&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--config-file&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;/test-db/postgresql.conf&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-D&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;/test-db
&lt;span class="go"&gt;  -k $(pwd)/test-db&lt;/span&gt;
&lt;span class="go"&gt;2023-11-18 19:35:16.680 GMT [3215547] LOG:  starting PostgreSQL 17devel on x86_64-pc-linux-gnu, compiled by gcc (GCC) 13.2.1 20230728 (Red Hat 13.2.1-1), 64-bit&lt;/span&gt;
&lt;span class="go"&gt;2023-11-18 19:35:16.681 GMT [3215547] LOG:  listening on IPv6 address &amp;quot;::1&amp;quot;, port 5432&lt;/span&gt;
&lt;span class="go"&gt;2023-11-18 19:35:16.681 GMT [3215547] LOG:  listening on IPv4 address &amp;quot;127.0.0.1&amp;quot;, port 5432&lt;/span&gt;
&lt;span class="go"&gt;2023-11-18 19:35:16.681 GMT [3215547] LOG:  listening on Unix socket &amp;quot;/tmp/.s.PGSQL.5432&amp;quot;&lt;/span&gt;
&lt;span class="go"&gt;2023-11-18 19:35:16.682 GMT [3215550] LOG:  database system was shut down at 2023-11-18 19:20:16 GMT&lt;/span&gt;
&lt;span class="go"&gt;2023-11-18 19:35:16.684 GMT [3215547] LOG:  database system is ready to accept connections&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Keep an eye on this foreground process since this is where &lt;code&gt;elog(LOG,
...)&lt;/code&gt; calls will show up.&lt;/p&gt;
&lt;p&gt;Now in a new window, create a &lt;code&gt;test.sql&lt;/code&gt; script that we can use to
exercise the hook:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;DROP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;EXISTS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INTO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;VALUES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;309&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Run &lt;code&gt;psql&lt;/code&gt; so we can trigger the hook:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;-h&lt;span class="w"&gt; &lt;/span&gt;localhost&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
&lt;span class="go"&gt;DROP TABLE&lt;/span&gt;
&lt;span class="go"&gt;CREATE TABLE&lt;/span&gt;
&lt;span class="go"&gt;INSERT 0 1&lt;/span&gt;
&lt;span class="go"&gt;  a&lt;/span&gt;
&lt;span class="go"&gt;-----&lt;/span&gt;
&lt;span class="go"&gt; 309&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(1 row)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And in the &lt;code&gt;postgres&lt;/code&gt; foreground process you should see a log:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;2023-11-19 17:42:03.045 GMT [3242321] LOG:  [pgexec] HOOKED SUCCESSFULLY!&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 17:42:03.045 GMT [3242321] STATEMENT:  INSERT INTO x VALUES (309);&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 17:42:03.045 GMT [3242321] LOG:  [pgexec] HOOKED SUCCESSFULLY!&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 17:42:03.045 GMT [3242321] STATEMENT:  SELECT a FROM x WHERE a &amp;gt; 1;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That's our hook! Interestingly only the &lt;code&gt;INSERT&lt;/code&gt; and &lt;code&gt;SELECT&lt;/code&gt;
statements show up, not the &lt;code&gt;DROP&lt;/code&gt; and &lt;code&gt;CREATE&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now let's see if we can reconstruct the query text from that first
argument, the &lt;code&gt;QueryDesc*&lt;/code&gt; that &lt;code&gt;pgexec_run_hook&lt;/code&gt; receives. And let's
simplify things for ourselves and only worry about reconstructing a
&lt;code&gt;SELECT&lt;/code&gt; query.&lt;/p&gt;
&lt;h3 id="&amp;lt;code&amp;gt;node&amp;lt;/code&amp;gt;s-and-&amp;lt;code&amp;gt;datum&amp;lt;/code&amp;gt;s"&gt;&lt;code&gt;Node&lt;/code&gt;s and &lt;code&gt;Datum&lt;/code&gt;s&lt;/h3&gt;&lt;p&gt;But first, let's talk about two fundemental ways data in Postgres
(code) is organized.&lt;/p&gt;
&lt;p&gt;Postgres code is extremely dynamic and, maybe relatedly,
fairly object-oriented. Almost every entity in Postgres is a
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/nodes.h#L128"&gt;&lt;code&gt;Node&lt;/code&gt;&lt;/a&gt;. While
values in Postgres that are exposed to users of Postgres are
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/postgres.h#L64"&gt;&lt;code&gt;Datum&lt;/code&gt;&lt;/a&gt;s.&lt;/p&gt;
&lt;p&gt;Each node has a type,
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/nodes.h#L26"&gt;&lt;code&gt;NodeTag&lt;/code&gt;&lt;/a&gt;,
that we can switch on to decide what to do. In contrast, &lt;code&gt;Datum&lt;/code&gt; has
no type. The type of the &lt;code&gt;Datum&lt;/code&gt; must be known by context before using
one of the transform functions like
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/postgres.h#L90"&gt;&lt;code&gt;DatumGetBool&lt;/code&gt;&lt;/a&gt;
to retrieve a C value from a &lt;code&gt;Datum&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A table is a &lt;code&gt;Node&lt;/code&gt;. A query plan is a &lt;code&gt;Node&lt;/code&gt;. A sequential scan is a
&lt;code&gt;Node&lt;/code&gt;. A join is a &lt;code&gt;Node&lt;/code&gt;. A literal in a query is a &lt;code&gt;Node&lt;/code&gt;. The
value for the literal in a query is a &lt;code&gt;Datum&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here is how The Internals of PostgreSQL book
&lt;a href="https://www.interdb.jp/pg/pgsql03.html"&gt;visualizes&lt;/a&gt; a query plan for
example:&lt;/p&gt;
&lt;p&gt;&lt;img alt="https://www.interdb.jp/pg/img/fig-3-04.png" src="https://www.interdb.jp/pg/img/fig-3-04.png" /&gt;&lt;/p&gt;
&lt;p&gt;Every box in that image is a &lt;code&gt;Node&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;And all &lt;code&gt;Node&lt;/code&gt;s in code I've seen share a common definition prefix
like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;SomeThing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;abstract&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// If the node is indeed abstract in the OOP sense.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;NodeTag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Many &lt;code&gt;Node&lt;/code&gt;s you'll see are abstract, like &lt;code&gt;Plan&lt;/code&gt;. But by printing out
&lt;code&gt;NodeTag&lt;/code&gt; and checking the value printed in
&lt;code&gt;src/include/nodes/nodetags.h&lt;/code&gt;, you can find the concrete type of the
&lt;code&gt;Node&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/include/nodes/nodetags.h&lt;/code&gt; is generated during a preprocessing
step. (Don't look if &lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/backend/nodes/gen_node_support.pl"&gt;regex in
Perl&lt;/a&gt;
worries you).&lt;/p&gt;
&lt;p&gt;We'll get back to &lt;code&gt;Node&lt;/code&gt;s later.&lt;/p&gt;
&lt;h3 id="what's-in-a-&amp;lt;code&amp;gt;querydesc&amp;lt;/code&amp;gt;?"&gt;What's in a &lt;code&gt;QueryDesc&lt;/code&gt;?&lt;/h3&gt;&lt;p&gt;Let's take a look at the
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/executor/execdesc.h#L33"&gt;&lt;code&gt;QueryDesc&lt;/code&gt;&lt;/a&gt;
struct:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;QueryDesc&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* These fields are provided by CreateQueryDesc */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;CmdType&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="cm"&gt;/* CMD_SELECT, CMD_UPDATE, etc. */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;PlannedStmt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;plannedstmt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="cm"&gt;/* planner's output (could be utility, too) */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sourceText&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="cm"&gt;/* source text of the query */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="cm"&gt;/* snapshot to use for query */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;crosscheck_snapshot&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* crosscheck for RI update/delete */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;DestReceiver&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="cm"&gt;/* the destination for tuple output */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;ParamListInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="cm"&gt;/* param values being passed in */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;QueryEnvironment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;queryEnv&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* query environment passed in */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;instrument_options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* OR of InstrumentOption flags */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* These fields are set by ExecutorStart */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;TupleDesc&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;tupDesc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="cm"&gt;/* descriptor for result tuples */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;EState&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;estate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="cm"&gt;/* executor's query-wide state */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;PlanState&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;planstate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="cm"&gt;/* tree of per-plan-node state */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* This field is set by ExecutorRun */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;already_executed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="cm"&gt;/* true if previously executed */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* This is always set NULL by the core system, but plugins can change it */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Instrumentation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;totaltime&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="cm"&gt;/* total time spent in ExecutorRun */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QueryDesc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/plannodes.h#L46"&gt;&lt;code&gt;PlannedStmt&lt;/code&gt;&lt;/a&gt;
field looks interesting. Let's take a look:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;PlannedStmt&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;no_equal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;no_query_jumble&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;NodeTag&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;CmdType&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;commandType&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* select|insert|update|delete|merge|utility */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;uint64&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;queryId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="cm"&gt;/* query identifier (copied from Query) */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;hasReturning&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="cm"&gt;/* is it insert|update|delete RETURNING? */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;hasModifyingCTE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* has insert|update|delete in WITH? */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;canSetTag&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="cm"&gt;/* do I set the command result tag? */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;transientPlan&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="cm"&gt;/* redo plan when TransactionXmin changes? */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;dependsOnRole&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="cm"&gt;/* is plan specific to current role? */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;parallelModeNeeded&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* parallel mode required to execute? */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;jitFlags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="cm"&gt;/* which forms of JIT should be performed */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Plan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;planTree&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="cm"&gt;/* tree of Plan nodes */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rtable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="cm"&gt;/* list of RangeTblEntry nodes */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;permInfos&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="cm"&gt;/* list of RTEPermissionInfo nodes for rtable&lt;/span&gt;
&lt;span class="cm"&gt;                                 * entries needing one */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* rtable indexes of target relations for INSERT/UPDATE/DELETE/MERGE */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;resultRelations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* integer list of RT indexes, or NIL */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;appendRelations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* list of AppendRelInfo nodes */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;subplans&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="cm"&gt;/* Plan trees for SubPlan expressions; note&lt;/span&gt;
&lt;span class="cm"&gt;                                 * that some could be NULL */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Bitmapset&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rewindPlanIDs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="cm"&gt;/* indices of subplans that require REWIND */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rowMarks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="cm"&gt;/* a list of PlanRowMark's */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;relationOids&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="cm"&gt;/* OIDs of relations the plan depends on */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;invalItems&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="cm"&gt;/* other dependencies, as PlanInvalItems */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;paramExecTypes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* type OIDs for PARAM_EXEC Params */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;utilityStmt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* non-null if this is utility stmt */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* statement location in source string (copied from Query) */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;stmt_location&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="cm"&gt;/* start location, or -1 if unknown */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;stmt_len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="cm"&gt;/* length in bytes; 0 means &amp;quot;rest of string&amp;quot; */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PlannedStmt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;struct Plan* planTree&lt;/code&gt; field in there looks like what we'd want. But
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/plannodes.h#L119"&gt;&lt;code&gt;Plan&lt;/code&gt;&lt;/a&gt;
is abstract:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Plan&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;abstract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;no_equal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;no_query_jumble&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;NodeTag&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So let's try printing out the &lt;code&gt;planTree-&amp;gt;type&lt;/code&gt; field and find the
&lt;code&gt;Node&lt;/code&gt; it is concretely. In &lt;code&gt;pgexec.c&lt;/code&gt; change the definition of
&lt;code&gt;print_plan&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;print_plan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QueryDesc&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;elog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[pgexec] HOOKED SUCCESSFULLY! %d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plannedstmt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;planTree&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Rebuild and reinstall the extension, and restart Postgres:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;make
&lt;span class="gp"&gt;$ &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/postgres&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--config-file&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;/test-db/postgresql.conf&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-D&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;/test-db
&lt;span class="go"&gt;  -k $(pwd)/test-db&lt;/span&gt;
&lt;span class="go"&gt;2023-11-18 19:35:16.680 GMT [3215547] LOG:  starting PostgreSQL 17devel on x86_64-pc-linux-gnu, compiled by gcc (GCC) 13.2.1 20230728 (Red Hat 13.2.1-1), 64-bit&lt;/span&gt;
&lt;span class="go"&gt;2023-11-18 19:35:16.681 GMT [3215547] LOG:  listening on IPv6 address &amp;quot;::1&amp;quot;, port 5432&lt;/span&gt;
&lt;span class="go"&gt;2023-11-18 19:35:16.681 GMT [3215547] LOG:  listening on IPv4 address &amp;quot;127.0.0.1&amp;quot;, port 5432&lt;/span&gt;
&lt;span class="go"&gt;2023-11-18 19:35:16.681 GMT [3215547] LOG:  listening on Unix socket &amp;quot;/tmp/.s.PGSQL.5432&amp;quot;&lt;/span&gt;
&lt;span class="go"&gt;2023-11-18 19:35:16.682 GMT [3215550] LOG:  database system was shut down at 2023-11-18 19:20:16 GMT&lt;/span&gt;
&lt;span class="go"&gt;2023-11-18 19:35:16.684 GMT [3215547] LOG:  database system is ready to accept connections&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And in another window run &lt;code&gt;psql&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;-h&lt;span class="w"&gt; &lt;/span&gt;localhost&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And check the logs from the &lt;code&gt;postgres&lt;/code&gt; process we just started and you
should notice:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;2023-11-19 17:46:18.834 GMT [3242495] LOG:  [pgexec] HOOKED SUCCESSFULLY! 322&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 17:46:18.834 GMT [3242495] STATEMENT:  SELECT a FROM x WHERE a &amp;gt; 1;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So &lt;code&gt;322&lt;/code&gt; is the &lt;code&gt;NodeTag&lt;/code&gt; for the &lt;code&gt;Plan&lt;/code&gt;. If we look that up in
Postgres's &lt;code&gt;src/include/nodes/nodetags.h&lt;/code&gt; (remember, this is generated
after &lt;code&gt;./configure &amp;amp;&amp;amp; make&lt;/code&gt; so I can't link you to it):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;' = 322'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src/include/nodes/nodetags.h
&lt;span class="go"&gt;        T_SeqScan = 322,&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Hey, that makes sense! A &lt;code&gt;SELECT&lt;/code&gt; without any indexes definitely
sounds like a sequential scan!&lt;/p&gt;
&lt;h3 id="walking-a-sequential-scan"&gt;Walking a sequential scan&lt;/h3&gt;&lt;p&gt;Let's take a look at the
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/plannodes.h#L394"&gt;&lt;code&gt;SeqScan&lt;/code&gt;&lt;/a&gt;
struct:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;SeqScan&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Scan&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SeqScan&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ok, that's not very interesting. Let's look at
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/plannodes.h#L382"&gt;&lt;code&gt;Scan&lt;/code&gt;&lt;/a&gt;
then:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Scan&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;abstract&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Plan&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;scanrelid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="cm"&gt;/* relid is index into the range table */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Scan&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That's interesting! &lt;code&gt;scanrelid&lt;/code&gt; represents the table we're scanning. I
don't know what "range table" means exactly. But there was a field on
the &lt;code&gt;PlannedStmt&lt;/code&gt; called &lt;code&gt;rtable&lt;/code&gt; that seems relevant.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;rtable&lt;/code&gt; was described as a
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/pg_list.h#L53"&gt;&lt;code&gt;List&lt;/code&gt;&lt;/a&gt;
of
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/parsenodes.h#L1019"&gt;&lt;code&gt;RangeTblEntry&lt;/code&gt;&lt;/a&gt;
nodes. And browsing around the file where &lt;code&gt;List&lt;/code&gt; is defined we can see
some nice methods for working with &lt;code&gt;List&lt;/code&gt;s, like &lt;code&gt;list_length()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let's print out the &lt;code&gt;scanrelid&lt;/code&gt; and let's check out the length of the
&lt;code&gt;rtable&lt;/code&gt; and see if it's filled out. Let's also restrict our
&lt;code&gt;print_plan&lt;/code&gt; code to only look at &lt;code&gt;SeqScan&lt;/code&gt; nodes. In &lt;code&gt;pgexec.c&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;print_plan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QueryDesc&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;SeqScan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Plan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plannedstmt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;planTree&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;T_SeqScan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;elog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[pgexec] Unsupported plan type.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SeqScan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;elog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[pgexec] relid: %d, rtable length: %d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scanrelid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list_length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plannedstmt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rtable&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Rebuild and reinstall the extension, and restart Postgres. (You can
find the instructions for this above if you've forgotten.) Re-run the
&lt;code&gt;test.sql&lt;/code&gt; script. And check the Postgres server logs. You should see:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;2023-11-19 18:00:34.184 GMT [3244438] LOG:  [pgexec] relid: 1, rtable length: 1&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 18:00:34.184 GMT [3244438] STATEMENT:  SELECT a FROM x WHERE a &amp;gt; 1;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Awesome! So &lt;code&gt;rtable&lt;/code&gt; does have data in it. There's only one table in
this query so its length makes sense to be &lt;code&gt;1&lt;/code&gt;. The &lt;code&gt;scanrelid&lt;/code&gt; being
&lt;code&gt;1&lt;/code&gt; also though is weird. Let's fetch the nth value from the &lt;code&gt;rtable&lt;/code&gt;
list using &lt;code&gt;scanrelid-1&lt;/code&gt; as the index.&lt;/p&gt;
&lt;p&gt;For the
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/parsenodes.h#L1019"&gt;&lt;code&gt;RangeTblEntry&lt;/code&gt;&lt;/a&gt;
itself, let's take a look:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RTEKind&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;RTE_RELATION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="cm"&gt;/* ordinary relation reference */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;RTE_SUBQUERY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="cm"&gt;/* subquery in FROM */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;RTE_JOIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;                   &lt;/span&gt;&lt;span class="cm"&gt;/* join */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;RTE_FUNCTION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="cm"&gt;/* function in FROM */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;RTE_TABLEFUNC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="cm"&gt;/* TableFunc(.., column list) */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;RTE_VALUES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;                 &lt;/span&gt;&lt;span class="cm"&gt;/* VALUES (&amp;lt;exprlist&amp;gt;), (&amp;lt;exprlist&amp;gt;), ... */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;RTE_CTE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="cm"&gt;/* common table expr (WITH list element) */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;RTE_NAMEDTUPLESTORE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="cm"&gt;/* tuplestore, e.g. for AFTER triggers */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;RTE_RESULT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;                 &lt;/span&gt;&lt;span class="cm"&gt;/* RTE represents an empty FROM clause; such&lt;/span&gt;
&lt;span class="cm"&gt;                                 * RTEs are added by the planner, they're not&lt;/span&gt;
&lt;span class="cm"&gt;                                 * present during parsing or rewriting */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RTEKind&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;RangeTblEntry&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;custom_read_write&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;custom_query_jumble&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;NodeTag&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;RTEKind&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;rtekind&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="cm"&gt;/* see above */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt;     * XXX the fields applicable to only some rte kinds should be merged into&lt;/span&gt;
&lt;span class="cm"&gt;     * a union.  I didn't do this yet because the diffs would impact a lot of&lt;/span&gt;
&lt;span class="cm"&gt;     * code that is being actively worked on.  FIXME someday.&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt;     * Fields valid for a plain relation RTE (else zero):&lt;/span&gt;
&lt;span class="cm"&gt;     *&lt;/span&gt;
&lt;span class="cm"&gt;     * rellockmode is really LOCKMODE, but it's declared int to avoid having&lt;/span&gt;
&lt;span class="cm"&gt;     * to include lock-related headers here.  It must be RowExclusiveLock if&lt;/span&gt;
&lt;span class="cm"&gt;     * the RTE is an INSERT/UPDATE/DELETE/MERGE target, else RowShareLock if&lt;/span&gt;
&lt;span class="cm"&gt;     * the RTE is a SELECT FOR UPDATE/FOR SHARE target, else AccessShareLock.&lt;/span&gt;
&lt;span class="cm"&gt;     *&lt;/span&gt;
&lt;span class="cm"&gt;     * Note: in some cases, rule expansion may result in RTEs that are marked&lt;/span&gt;
&lt;span class="cm"&gt;     * with RowExclusiveLock even though they are not the target of the&lt;/span&gt;
&lt;span class="cm"&gt;     * current query; this happens if a DO ALSO rule simply scans the original&lt;/span&gt;
&lt;span class="cm"&gt;     * target table.  We leave such RTEs with their original lockmode so as to&lt;/span&gt;
&lt;span class="cm"&gt;     * avoid getting an additional, lesser lock.&lt;/span&gt;
&lt;span class="cm"&gt;     *&lt;/span&gt;
&lt;span class="cm"&gt;     * perminfoindex is 1-based index of the RTEPermissionInfo belonging to&lt;/span&gt;
&lt;span class="cm"&gt;     * this RTE in the containing struct's list of same; 0 if permissions need&lt;/span&gt;
&lt;span class="cm"&gt;     * not be checked for this RTE.&lt;/span&gt;
&lt;span class="cm"&gt;     *&lt;/span&gt;
&lt;span class="cm"&gt;     * As a special case, relid, relkind, rellockmode, and perminfoindex can&lt;/span&gt;
&lt;span class="cm"&gt;     * also be set (nonzero) in an RTE_SUBQUERY RTE.  This occurs when we&lt;/span&gt;
&lt;span class="cm"&gt;     * convert an RTE_RELATION RTE naming a view into an RTE_SUBQUERY&lt;/span&gt;
&lt;span class="cm"&gt;     * containing the view's query.  We still need to perform run-time locking&lt;/span&gt;
&lt;span class="cm"&gt;     * and permission checks on the view, even though it's not directly used&lt;/span&gt;
&lt;span class="cm"&gt;     * in the query anymore, and the most expedient way to do that is to&lt;/span&gt;
&lt;span class="cm"&gt;     * retain these fields from the old state of the RTE.&lt;/span&gt;
&lt;span class="cm"&gt;     *&lt;/span&gt;
&lt;span class="cm"&gt;     * As a special case, RTE_NAMEDTUPLESTORE can also set relid to indicate&lt;/span&gt;
&lt;span class="cm"&gt;     * that the tuple format of the tuplestore is the same as the referenced&lt;/span&gt;
&lt;span class="cm"&gt;     * relation.  This allows plans referencing AFTER trigger transition&lt;/span&gt;
&lt;span class="cm"&gt;     * tables to be invalidated if the underlying table is altered.&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Oid&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;relid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="cm"&gt;/* OID of the relation */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;relkind&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="cm"&gt;/* relation kind (see pg_class.relkind) */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;rellockmode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* lock level that query requires on the rel */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;TableSampleClause&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tablesample&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="cm"&gt;/* sampling info, or NULL */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;perminfoindex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In &lt;code&gt;SELECT a FROM x&lt;/code&gt;, &lt;code&gt;x&lt;/code&gt; should be a plain relation RTE (to use the
terminology there). So we can add a guard that validates that. But we
don't get a &lt;code&gt;Relation&lt;/code&gt;. (You might remember from my &lt;a href="https://notes.eatonphil.com/2023-11-01-postgres-table-access-methods.html"&gt;previous
post&lt;/a&gt;
that &lt;code&gt;Relation&lt;/code&gt; is where we can finally see the table name.)&lt;/p&gt;
&lt;p&gt;We get an &lt;code&gt;Oid&lt;/code&gt; for the &lt;code&gt;Relation&lt;/code&gt;. So we need to find a way to lookup
a &lt;code&gt;Relation&lt;/code&gt; from an &lt;code&gt;Oid&lt;/code&gt;. And by grepping around in Postgres (or via
judicious use of ChatGPT, I confess), we can notice
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/backend/utils/cache/relcache.c#L2056"&gt;&lt;code&gt;RelationIdGetRelation&lt;/code&gt;&lt;/a&gt;
that takes an &lt;code&gt;Oid&lt;/code&gt; and returns a &lt;code&gt;Relation&lt;/code&gt;. Notice also that the
comment says we should close the relation when we're done with
&lt;code&gt;RelationClose&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So putting it altogether (and again, reusing some code from that
previous post), we can print out the table name.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;print_plan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QueryDesc&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;SeqScan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;RangeTblEntry&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tablename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Plan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plannedstmt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;planTree&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;T_SeqScan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;elog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[pgexec] Unsupported plan type.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SeqScan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list_nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plannedstmt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rtable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scanrelid&lt;/span&gt;&lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rtekind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RTE_RELATION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;elog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[pgexec] Unsupported FROM type: %d.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rtekind&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RelationIdGetRelation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;relid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;tablename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NameStr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rd_rel&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;elog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[pgexec] SELECT [todo] FROM %s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tablename&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;RelationClose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You'll also need to add a new &lt;code&gt;#include&lt;/code&gt; for
&lt;code&gt;utils/rel.h&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Rebuild and reinstall the extension, and restart Postgres. Re-run the
&lt;code&gt;test.sql&lt;/code&gt; script. Check the Postgres server logs and you should see:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;2023-11-19 18:36:03.986 GMT [3246777] LOG:  [pgexec] SELECT [todo] FROM x&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 18:36:03.986 GMT [3246777] STATEMENT:  SELECT a FROM x WHERE a &amp;gt; 1;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Fantastic! Before we get into walking the &lt;code&gt;SELECT&lt;/code&gt; columns and the
(optional) &lt;code&gt;WHERE&lt;/code&gt; clause, let's do some quick refactoring.&lt;/p&gt;
&lt;h3 id="a-string-builder"&gt;A string builder&lt;/h3&gt;&lt;p&gt;Let's add a little string builder library so we can emit a single
string we build up to a single &lt;code&gt;elog()&lt;/code&gt; call.&lt;/p&gt;
&lt;p&gt;I wrote this ahead of time and won't explain it here since the details
aren't relevant.&lt;/p&gt;
&lt;p&gt;Just copy this and paste near the top of &lt;code&gt;pgexec.c&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;malloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_resize_to_fit_additional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;additional&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;newsize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;additional&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;additional&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;newsize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;additional&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;malloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;newsize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;memcpy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;newsize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_appendz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;strlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_resize_to_fit_additional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;memcpy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;restrict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__attribute__&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gnu_printf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;restrict&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// First figure out how long the result will be.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;va_list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;arglist&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;va_start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arglist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vsnprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;arglist&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// TODO: error handling.&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Resize to fit result.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_resize_to_fit_additional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Actually do the printf into buf.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;va_end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arglist&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;va_start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arglist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vsprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;arglist&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// TODO: error handling.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;va_end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arglist&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_cstring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;zero&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prev_offset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;zero&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Offset should stay the same. This is a fake NULL.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prev_offset&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Next we'll modify &lt;code&gt;print_plan()&lt;/code&gt; in &lt;code&gt;pgexec.c&lt;/code&gt; to use it, and add stubs
for printing the &lt;code&gt;SELECT&lt;/code&gt; columns and &lt;code&gt;WHERE&lt;/code&gt; clauses.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QueryDesc&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Plan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_appendz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; [where todo]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_select_columns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QueryDesc&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Plan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_appendz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[columns todo]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;print_plan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QueryDesc&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;SeqScan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;RangeTblEntry&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tablename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Plan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plannedstmt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;planTree&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;T_SeqScan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;elog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[pgexec] Unsupported plan type.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SeqScan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list_nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plannedstmt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rtable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scanrelid&lt;/span&gt;&lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rtekind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RTE_RELATION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;elog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[pgexec] Unsupported FROM type: %d.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rtekind&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RelationIdGetRelation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;relid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;tablename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NameStr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rd_rel&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_appendz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;SELECT &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_print_select_columns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; FROM %s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tablename&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_print_where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;elog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[pgexec] %s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buffer_cstring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;RelationClose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we just need to implement the &lt;code&gt;buffer_print_where&lt;/code&gt; and
&lt;code&gt;buffer_print_select_columns&lt;/code&gt; functions and our walking infrastructure
will be done! For now. :)&lt;/p&gt;
&lt;h3 id="walking-the-&amp;lt;code&amp;gt;where&amp;lt;/code&amp;gt;-clause"&gt;Walking the &lt;code&gt;WHERE&lt;/code&gt; clause&lt;/h3&gt;&lt;p&gt;If you remember back to the &lt;code&gt;SeqScan&lt;/code&gt; and &lt;code&gt;Scan&lt;/code&gt; nodes, they were both
basically empty. They had a &lt;code&gt;Plan&lt;/code&gt; and a &lt;code&gt;scanrelid&lt;/code&gt;. So the rest of
the &lt;code&gt;SELECT&lt;/code&gt; info must be in the &lt;code&gt;Plan&lt;/code&gt; since it wasn't in the &lt;code&gt;Scan&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let's look at
&lt;a href="https://github.com/postgres/postgres/blob/master/src/include/nodes/plannodes.h#L119"&gt;&lt;code&gt;Plan&lt;/code&gt;&lt;/a&gt;
again. One part that stands out is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt;     * Common structural data for all Plan types.&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;plan_node_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="cm"&gt;/* unique across entire final plan tree */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;targetlist&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="cm"&gt;/* target list to be computed at this node */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;qual&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="cm"&gt;/* implicitly-ANDed qual conditions */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Plan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;lefttree&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="cm"&gt;/* input plan tree(s) */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Plan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;righttree&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;initPlan&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="cm"&gt;/* Init Plan nodes (un-correlated expr&lt;/span&gt;
&lt;span class="cm"&gt;                                 * subselects) */&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;qual&lt;/code&gt; kinda looks like a &lt;code&gt;WHERE&lt;/code&gt; clause. (And &lt;code&gt;targetlist&lt;/code&gt; kinda
looks like the columns the &lt;code&gt;SELECT&lt;/code&gt; pulls).&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/pg_list.h#L53"&gt;&lt;code&gt;List&lt;/code&gt;&lt;/a&gt;s
just contain void pointers, so we can't tell what the type of &lt;code&gt;qual&lt;/code&gt;
or &lt;code&gt;targetlist&lt;/code&gt; children are. But I'm going to make a wild guess they
are &lt;code&gt;Node&lt;/code&gt;s.&lt;/p&gt;
&lt;p&gt;There's even a nice helper that casts void pointers to &lt;code&gt;Node*&lt;/code&gt; and
pulls out the type,
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/nodes.h#L133"&gt;&lt;code&gt;nodeTag()&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And reading around &lt;code&gt;pg_list.h&lt;/code&gt; shows some interesting helper utilities
like
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/pg_list.h#L373"&gt;&lt;code&gt;foreach&lt;/code&gt;&lt;/a&gt;
that we can use to iterate the list.&lt;/p&gt;
&lt;p&gt;Let's try printing out the type of &lt;code&gt;qual&lt;/code&gt;'s members.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QueryDesc&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Plan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ListCell&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;qual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_appendz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; WHERE &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;qual&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;buffer_appendz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; AND &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[node: %d]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nodeTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lfirst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p class="note"&gt;
  Notice any &lt;a href="https://twitter.com/eatonphil/status/1726265982094819631"&gt;vestiges
  of LISP&lt;/a&gt;?
&lt;/p&gt;&lt;p&gt;Rebuild and reinstall the extension, and restart Postgres. Re-run the
&lt;code&gt;test.sql&lt;/code&gt; script. Check the Postgres server logs and you should see:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;2023-11-19 19:17:00.879 GMT [3250850] LOG:  [pgexec] SELECT [columns todo] FROM x WHERE [node: 15]&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 19:17:00.879 GMT [3250850] STATEMENT:  SELECT a FROM x WHERE a &amp;gt; 1;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Well, our code didn't crash! So the guess about &lt;code&gt;qual&lt;/code&gt; &lt;code&gt;List&lt;/code&gt; entries
being &lt;code&gt;Node&lt;/code&gt;s seems right. Let's look up that node type in the
Postgres repo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;' = 15,'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src/include/nodes/nodetags.h
&lt;span class="go"&gt;        T_OpExpr = 15,&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Woot! That is exactly what I'd expect the &lt;code&gt;WHERE&lt;/code&gt; clause here to be.&lt;/p&gt;
&lt;p&gt;Now that we know &lt;code&gt;qual&lt;/code&gt; is a &lt;code&gt;List&lt;/code&gt; of &lt;code&gt;Node&lt;/code&gt;s, let's do a bit of
refactoring since &lt;code&gt;targetlist&lt;/code&gt; will probably also be a &lt;code&gt;List&lt;/code&gt; of
&lt;code&gt;Node&lt;/code&gt;s. Back in &lt;code&gt;pgexec.c&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_opexpr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OpExpr&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[opexpr: todo]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodeTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;T_OpExpr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_print_opexpr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OpExpr&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[Unknown: %d]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nodeTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ListCell&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;buffer_appendz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sep&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_print_expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;lfirst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QueryDesc&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Plan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;qual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_appendz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; WHERE &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_print_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;qual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; AND &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And let's check out &lt;code&gt;OpExpr&lt;/code&gt;!&lt;/p&gt;
&lt;h3 id="walking-&amp;lt;code&amp;gt;opexpr&amp;lt;/code&amp;gt;"&gt;Walking &lt;code&gt;OpExpr&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;Take a look at the definition of
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/primnodes.h#L748"&gt;&lt;code&gt;OpExpr&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;OpExpr&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Expr&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;xpr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* PG_OPERATOR OID of the operator */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Oid&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;opno&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* PG_PROC OID of underlying function */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Oid&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;opfuncid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;equal_ignore_if_zero&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* PG_TYPE OID of result value */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Oid&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;opresulttype&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* true if operator returns set */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;opretset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* OID of collation of result */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Oid&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;opcollid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* OID of collation that operator should use */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Oid&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;inputcollid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* arguments to the operator (1 or 2) */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* token location, or -1 if unknown */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OpExpr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The important fields are &lt;code&gt;opno&lt;/code&gt;, the &lt;code&gt;Oid&lt;/code&gt; of the operator, and
&lt;code&gt;args&lt;/code&gt;. &lt;code&gt;args&lt;/code&gt; looks like another &lt;code&gt;List&lt;/code&gt; of &lt;code&gt;Node&lt;/code&gt;s so we already know
how to handle that.&lt;/p&gt;
&lt;p&gt;But how do we find the string name of the operator? Presumably there's
infrastructure like &lt;code&gt;RelationIdGetRelation&lt;/code&gt; that takes an &lt;code&gt;Oid&lt;/code&gt; and
gets us an operator object.&lt;/p&gt;
&lt;p&gt;Well I got stuck here as well. Again, thankfully, ChatGPT gave me some
suggestions. There's no great story for how I got it working. So here's
&lt;code&gt;buffer_print_opexpr&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_op&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OpExpr&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;HeapTuple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;opertup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SearchSysCache1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OPEROID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ObjectIdGetDatum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;opno&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_print_expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lfirst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list_nth_cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HeapTupleIsValid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opertup&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Form_pg_operator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;operator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Form_pg_operator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;GETSTRUCT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opertup&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; %s &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NameStr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;operator&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;oprname&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;ReleaseSysCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opertup&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[Unknown operation: %d]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;opno&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// TODO: Support single operand operations.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_print_expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lfirst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list_nth_cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And add the following two includes to the top of &lt;code&gt;pgexec.c&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;catalog/pg_operator.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;utils/syscache.h&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Rebuild and reinstall the extension, and restart Postgres. Re-run the
&lt;code&gt;test.sql&lt;/code&gt; script. Check the Postgres server logs and you should see:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;2023-11-19 19:42:52.916 GMT [3252974] LOG:  [pgexec] SELECT [columns todo] FROM x WHERE [Unknown: 6] &amp;gt; [Unknown: 7]&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 19:42:52.916 GMT [3252974] STATEMENT:  SELECT a FROM x WHERE a &amp;gt; 1;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And we continue to make progress! Let's look up the type of these two
unknown nodes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;' = 6,'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src/include/nodes/nodetags.h
&lt;span class="go"&gt;        T_Var = 6,&lt;/span&gt;
&lt;span class="gp"&gt;$ &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;' = 7,'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src/include/nodes/nodetags.h
&lt;span class="go"&gt;        T_Const = 7,&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's deal with &lt;code&gt;Const&lt;/code&gt; first.&lt;/p&gt;
&lt;h3 id="walking-&amp;lt;code&amp;gt;const&amp;lt;/code&amp;gt;"&gt;Walking &lt;code&gt;Const&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;If we take a look at the
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/primnodes.h#L292"&gt;&lt;code&gt;Const&lt;/code&gt;&lt;/a&gt;
definition:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Const&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;custom_copy_equal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;custom_read_write&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Expr&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;xpr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* pg_type OID of the constant's datatype */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Oid&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;consttype&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* typmod value, if any */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;int32&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;consttypmod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* OID of collation, or InvalidOid if none */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Oid&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;constcollid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* typlen of the constant's datatype */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;constlen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* the constant's value */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Datum&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;constvalue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* whether the constant is null (if true, constvalue is undefined) */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;constisnull&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt;     * Whether this datatype is passed by value.  If true, then all the&lt;/span&gt;
&lt;span class="cm"&gt;     * information is stored in the Datum.  If false, then the Datum contains&lt;/span&gt;
&lt;span class="cm"&gt;     * a pointer to the information.&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;constbyval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt;     * token location, or -1 if unknown.  All constants are tracked as&lt;/span&gt;
&lt;span class="cm"&gt;     * locations in query jumbling, to be marked as parameters.&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_location&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It looks like we need to switch on the &lt;code&gt;consttype&lt;/code&gt; (an &lt;code&gt;Oid&lt;/code&gt;) to
figure out how to interpret the &lt;code&gt;constvalue&lt;/code&gt; (a &lt;code&gt;Datum&lt;/code&gt;). Remember I
mentioned earlier that how to interpret a &lt;code&gt;Datum&lt;/code&gt; is dependent on
context. &lt;code&gt;consttype&lt;/code&gt; is the context here.&lt;/p&gt;
&lt;p&gt;In this case, although &lt;code&gt;consttype&lt;/code&gt; is an &lt;code&gt;Oid&lt;/code&gt; and we had to use
Postgres infrastructure to look up the &lt;code&gt;Oid&lt;/code&gt;'s corresponding object,
there are some builtin types and the literals we've queried with are
among them.&lt;/p&gt;
&lt;p&gt;We can simply check if &lt;code&gt;consttype == INT4OID&lt;/code&gt; and the interpret the
&lt;code&gt;Datum&lt;/code&gt; as an &lt;code&gt;int32&lt;/code&gt; if so. &lt;code&gt;DatumGetInt32&lt;/code&gt; will get us that &lt;code&gt;int32&lt;/code&gt;
in that case.&lt;/p&gt;
&lt;p&gt;To support the new &lt;code&gt;Const&lt;/code&gt; type, we'll add a case in
&lt;code&gt;buffer_print_expr&lt;/code&gt; to look for a &lt;code&gt;T_Const&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodeTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;T_Const&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_print_const&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Const&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;T_OpExpr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_print_opexpr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OpExpr&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[Unknown: %d]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nodeTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And add a new function, &lt;code&gt;buffer_print_const&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_const&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Const&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cnst&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cnst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;consttype&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;INT4OID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;int32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DatumGetInt32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cnst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;constvalue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;%d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[Unknown consttype oid: %d]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cnst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;consttype&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Rebuild and reinstall the extension, and restart Postgres. Re-run the
&lt;code&gt;test.sql&lt;/code&gt; script. Check the Postgres server logs and you should see:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;2023-11-19 19:53:47.922 GMT [3253746] LOG:  [pgexec] SELECT [columns todo] FROM x WHERE [Unknown: 6] &amp;gt; 1&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 19:53:47.922 GMT [3253746] STATEMENT:  SELECT a FROM x WHERE a &amp;gt; 1;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Great! Now we just have to tackle &lt;code&gt;T_Var&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="walking-&amp;lt;code&amp;gt;var&amp;lt;/code&amp;gt;"&gt;Walking &lt;code&gt;Var&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;Let's take a look at the definition of &lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/primnodes.h#L233"&gt;&lt;code&gt;Var&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Var&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Expr&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;xpr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt;     * index of this var's relation in the range table, or&lt;/span&gt;
&lt;span class="cm"&gt;     * INNER_VAR/OUTER_VAR/etc&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;varno&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt;     * attribute number of this var, or zero for all attrs (&amp;quot;whole-row Var&amp;quot;)&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AttrNumber&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;varattno&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* pg_type OID for the type of this var */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Oid&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;vartype&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* pg_attribute typmod value */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;int32&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;vartypmod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* OID of collation, or InvalidOid if none */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Oid&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;varcollid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt;     * RT indexes of outer joins that can replace the Var's value with null.&lt;/span&gt;
&lt;span class="cm"&gt;     * We can omit varnullingrels in the query jumble, because it's fully&lt;/span&gt;
&lt;span class="cm"&gt;     * determined by varno/varlevelsup plus the Var's query location.&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Bitmapset&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;varnullingrels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt;     * for subquery variables referencing outer relations; 0 in a normal var,&lt;/span&gt;
&lt;span class="cm"&gt;     * &amp;gt;0 means N levels up&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;varlevelsup&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt;     * varnosyn/varattnosyn are ignored for equality, because Vars with&lt;/span&gt;
&lt;span class="cm"&gt;     * different syntactic identifiers are semantically the same as long as&lt;/span&gt;
&lt;span class="cm"&gt;     * their varno/varattno match.&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* syntactic relation index (0 if unknown) */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;varnosyn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;equal_ignore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* syntactic attribute number */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AttrNumber&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;varattnosyn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;equal_ignore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* token location, or -1 if unknown */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Var&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It looks like this refers to a relation in the range table list
again. So this means we need to have access to the full &lt;code&gt;PlannedStmt&lt;/code&gt;
so we can read its &lt;code&gt;rtable&lt;/code&gt; field again to find the table. Then we
need to look up the &lt;code&gt;Relation&lt;/code&gt; for the table and then we can use the
&lt;code&gt;Var&lt;/code&gt;'s &lt;code&gt;varattno&lt;/code&gt; field to pick the nth attribute from the relation
and get its string representation.&lt;/p&gt;
&lt;p&gt;However, ChatGPT found a slightly higher-level function:
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/backend/utils/cache/lsyscache.c#L826"&gt;&lt;code&gt;get_attname()&lt;/code&gt;&lt;/a&gt;
that takes a relation &lt;code&gt;Oid&lt;/code&gt; and an attribute index and returns the
string name of the column.&lt;/p&gt;
&lt;p&gt;So here's what &lt;code&gt;buffer_print_var&lt;/code&gt; looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PlannedStmt&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Var&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;RangeTblEntry&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list_nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rtable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;varno&lt;/span&gt;&lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rtekind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RTE_RELATION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;elog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[Unsupported relation type for var: %d].&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rtekind&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get_attname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;relid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;varattno&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_appendz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;pfree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You'll also need to add another &lt;code&gt;#include&lt;/code&gt; for &lt;code&gt;utils/lsyscache.h&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let's add the &lt;code&gt;case T_Var:&lt;/code&gt; check in &lt;code&gt;buffer_print_expr&lt;/code&gt;, and also
feed the &lt;code&gt;PlannedStmt*&lt;/code&gt; through all the necessary &lt;code&gt;buffer_print_X&lt;/code&gt;
functions:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PlannedStmt&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PlannedStmt&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_opexpr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PlannedStmt&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OpExpr&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;HeapTuple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;opertup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SearchSysCache1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OPEROID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ObjectIdGetDatum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;opno&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_print_expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lfirst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list_nth_cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HeapTupleIsValid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opertup&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Form_pg_operator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;operator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Form_pg_operator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;GETSTRUCT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opertup&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; %s &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NameStr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;operator&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;oprname&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;ReleaseSysCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opertup&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[Unknown operation: %d]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;opno&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// TODO: Support single operand operations.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_print_expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lfirst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list_nth_cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_const&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Const&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cnst&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cnst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;consttype&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;INT4OID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;int32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DatumGetInt32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cnst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;constvalue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;%d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[Unknown consttype oid: %d]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cnst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;consttype&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PlannedStmt&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Var&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;RangeTblEntry&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list_nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rtable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;varno&lt;/span&gt;&lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rtekind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RTE_RELATION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;elog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[Unsupported relation type for var: %d].&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rtekind&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get_attname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;relid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;varattno&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_appendz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;pfree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PlannedStmt&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodeTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;T_Const&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_print_const&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Const&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;T_Var&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_print_var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Var&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;T_OpExpr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_print_opexpr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OpExpr&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[Unknown: %d]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nodeTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PlannedStmt&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ListCell&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;buffer_appendz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sep&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_print_expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;lfirst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QueryDesc&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Plan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;qual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_appendz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; WHERE &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_print_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plannedstmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;qual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; AND &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Rebuild and reinstall the extension, and restart Postgres. Re-run the
&lt;code&gt;test.sql&lt;/code&gt; script. Check the Postgres server logs and you should see:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;2023-11-19 20:03:14.351 GMT [3254458] LOG:  [pgexec] SELECT [columns todo] FROM x WHERE a &amp;gt; 1&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 20:03:14.351 GMT [3254458] STATEMENT:  SELECT a FROM x WHERE a &amp;gt; 1;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Huzzah!&lt;/p&gt;
&lt;h3 id="walking-the-column-list"&gt;Walking the column list&lt;/h3&gt;&lt;p&gt;Let's get rid of &lt;code&gt;[columns todo]&lt;/code&gt;. We already had the idea that &lt;code&gt;List*
targetlist&lt;/code&gt; on the &lt;code&gt;Plan&lt;/code&gt; struct was a list of expression
&lt;code&gt;Node&lt;/code&gt;s. Let's try it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_select_columns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QueryDesc&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Plan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;targetlist&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_print_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plannedstmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;targetlist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;, &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Rebuild and reinstall the extension, and restart Postgres. Re-run the
&lt;code&gt;test.sql&lt;/code&gt; script. Check the Postgres server logs and you should see:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;2023-11-19 20:12:48.091 GMT [3255398] LOG:  [pgexec] SELECT [Unknown: 53] FROM x WHERE a &amp;gt; 1&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 20:12:48.091 GMT [3255398] STATEMENT:  SELECT a FROM x WHERE a &amp;gt; 1;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Hmm. Let's look up &lt;code&gt;Node&lt;/code&gt; &lt;code&gt;53&lt;/code&gt; in Postgres:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;' = 53,'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src/include/nodes/nodetags.h
&lt;span class="go"&gt;        T_TargetEntry = 53,&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Based on the definition of
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/primnodes.h#L1918"&gt;&lt;code&gt;TargetEntry&lt;/code&gt;&lt;/a&gt;,
it looks like we can ignore most of the fields (because we don't need
to handle &lt;code&gt;SELECT a AS b&lt;/code&gt; yet) and just proxy the child &lt;code&gt;expr&lt;/code&gt; field.&lt;/p&gt;
&lt;p&gt;Let's add a &lt;code&gt;case T_TargetEntry&lt;/code&gt; to &lt;code&gt;buffer_print_expr&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PlannedStmt&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodeTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;T_Const&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_print_const&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Const&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;T_Var&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_print_var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Var&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;T_TargetEntry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_print_expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)((&lt;/span&gt;&lt;span class="n"&gt;TargetEntry&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;T_OpExpr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_print_opexpr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OpExpr&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[Unknown: %d]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nodeTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Rebuild and reinstall the extension, and restart Postgres. Re-run the
&lt;code&gt;test.sql&lt;/code&gt; script. Check the Postgres server logs and:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;2023-11-19 20:17:51.114 GMT [3257827] LOG:  [pgexec] SELECT a FROM x WHERE a &amp;gt; 1&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 20:17:51.114 GMT [3257827] STATEMENT:  SELECT a FROM x WHERE a &amp;gt; 1;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We did it!&lt;/p&gt;
&lt;h3 id="variations"&gt;Variations&lt;/h3&gt;&lt;p&gt;Let's try out some other queries to make sure this wasn't just luck.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;-h&lt;span class="w"&gt; &lt;/span&gt;localhost&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'SELECT a + 1 FROM x'&lt;/span&gt;
&lt;span class="go"&gt; ?column?&lt;/span&gt;
&lt;span class="go"&gt;----------&lt;/span&gt;
&lt;span class="go"&gt;      310&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(1 row)&lt;/span&gt;

&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;-h&lt;span class="w"&gt; &lt;/span&gt;localhost&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'SELECT a + 1 FROM x WHERE 2 &amp;gt; a'&lt;/span&gt;
&lt;span class="go"&gt; ?column?&lt;/span&gt;
&lt;span class="go"&gt;----------&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(0 rows)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And back in the Postgres server logs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;2023-11-19 20:19:28.057 GMT [3257874] LOG:  [pgexec] SELECT a + 1 FROM x&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 20:19:28.057 GMT [3257874] STATEMENT:  SELECT a + 1 FROM x&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 20:19:30.474 GMT [3257878] LOG:  [pgexec] SELECT a + 1 FROM x WHERE 2 &amp;gt; a&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 20:19:30.474 GMT [3257878] STATEMENT:  SELECT a + 1 FROM x WHERE 2 &amp;gt; a&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Not bad!&lt;/p&gt;
&lt;h3 id="next-steps"&gt;Next steps&lt;/h3&gt;&lt;p&gt;Printing out the statement here isn't incredibly useful. But it
establishes a basis for future work that might avoid Postgres's query
execution engine and do the execution ourselves, or to proxy execution
to another system.&lt;/p&gt;
&lt;h3 id="postscript:-on-chatgpt"&gt;Postscript: On ChatGPT&lt;/h3&gt;&lt;p&gt;My recent Postgres explorations would have been basically impossible
if it weren't for being able to ask ChatGPT simple, stupid questions
like "How do I get from a Postgres &lt;code&gt;Var&lt;/code&gt; to a column name".&lt;/p&gt;
&lt;p&gt;It isn't always right. It doesn't always give great code. Actually, it
normally gives pretty weird code. But it's been extremely useful for
quick iteration when I get stuck.&lt;/p&gt;
&lt;p&gt;The only other place the information exists is in small blog posts
around the internet, the Postgres mailing lists (that so far for me
hasn't been super responsive), and the code itself.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I've been on a Postgres roll. Let's dig into interpreting a Postgres query plan in preparation for future work on completely diverting the flow of Postgres query execution using execution hooks!&lt;a href="https://t.co/EZrgoIiTuX"&gt;https://t.co/EZrgoIiTuX&lt;/a&gt; &lt;a href="https://t.co/7S6d6olPX8"&gt;pic.twitter.com/7S6d6olPX8&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1726336428626587710?ref_src=twsrc%5Etfw"&gt;November 19, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Sun, 19 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2023-11-19-exploring-a-postgres-query-plan.html</guid></item><item><title>An attempt at creating a DIY baby monitor</title><link>https://blog.ovalerio.net/archives/2677</link><description>Occasionally, &amp;#8220;modern&amp;#8221; baby monitors hit the news, but not for the best reasons. Sometimes for the lack of security, other times for exploitative practices, etc. This also applies to many other categories of products that we can include in the so-called &amp;#8220;Internet of Things&amp;#8221;. After reading a recent article about the topic, some comments with [&amp;#8230;]</description><author>Gonçalo Valério</author><pubDate>Sun, 19 Nov 2023 00:12:39 GMT</pubDate><guid isPermaLink="true">https://blog.ovalerio.net/archives/2677</guid></item><item><title>Why was Sam Altman fired from OpenAI?</title><link>https://olshansky.info/posts/why-was-sam-altman-fired-from-openai/</link><description>tl;dr AGI (Artificial General Intelligence) may be closer than we think.</description><author>🦉 olshansky 🦁</author><pubDate>Sat, 18 Nov 2023 22:32:16 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/posts/why-was-sam-altman-fired-from-openai/</guid></item><item><title>[TIL] nix develop -c $SHELL: Run zsh with 'nix develop'</title><link>https://davi.sh/til/nix/nix-develop-c/</link><description>&lt;p&gt;I built my &lt;a href="https://github.com/davish/website-astro/blob/main/flake.nix"&gt;development flake for this website&lt;/a&gt; with the &lt;a href="https://github.com/DeterminateSystems/fh"&gt;FlakeHub CLI&lt;/a&gt;. That flake uses &lt;a href="https://ryantm.github.io/nixpkgs/builders/special/mkshell/"&gt;&lt;code&gt;pkgs.mkShell&lt;/code&gt;&lt;/a&gt; to load in binary dependencies to the development environment. I run zsh defined and configured by &lt;code&gt;nix-darwin&lt;/code&gt;, so it was pretty jarring getting thrown into a plain bash shell when running &lt;code&gt;nix develop&lt;/code&gt;. Luckily the fix is simple. Rather than just running &lt;code&gt;nix develop&lt;/code&gt;, run:&lt;/p&gt;
&lt;pre class="astro-code github-dark" style="background-color: #24292e; color: #e1e4e8;" tabindex="0"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;nix&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; develop&lt;/span&gt;&lt;span style="color: #79B8FF;"&gt; -c&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; $SHELL&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and your normal shell will be run instead of the default bash shell.&lt;/p&gt;
&lt;p&gt;(via &lt;a href="https://discourse.nixos.org/t/using-nix-develop-opens-bash-instead-of-zsh/25075"&gt;this discussion&lt;/a&gt;)&lt;/p&gt;</description><author>Davis Haupt's Blog</author><pubDate>Sat, 18 Nov 2023 19:00:00 GMT</pubDate><guid isPermaLink="true">https://davi.sh/til/nix/nix-develop-c/</guid></item><item><title>Every software is messy and has skeletons</title><link>https://vadimkravcenko.com/shorts/every-app-has-its-skeletons/</link><description>&lt;p&gt;Question:&lt;/p&gt;
&lt;/p&gt;
&lt;p&gt; Answer:&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://vadimkravcenko.com/shorts/every-app-has-its-skeletons/"&gt;Every software is messy and has skeletons&lt;/a&gt; appeared first on &lt;a href="https://vadimkravcenko.com"&gt;Vadim Kravcenko&lt;/a&gt;.&lt;/p&gt;</description><author>Vadim Kravcenko</author><pubDate>Sat, 18 Nov 2023 14:37:12 GMT</pubDate><guid isPermaLink="true">https://vadimkravcenko.com/shorts/every-app-has-its-skeletons/</guid></item><item><title>A performant embedding processing pipeline &amp;amp; tutorial for big XML/HTML data dumps</title><link>https://geo.rocks/post/big-data-xml-embeddings/</link><description>&lt;p&gt;&lt;img alt="futuristic_GPU_title" src="https://geo.rocks/img/futuristic_GPU_title.jpeg" /&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s process 1.369.841 single English XML/HTML files and index them with &lt;a href="https://huggingface.co/BAAI/bge-base-en-v1.5"&gt;BAAI/bge-base-en-v1.5&lt;/a&gt; embeddings for semantic search! Sounds fun? Let&amp;rsquo;s go!&lt;/p&gt;</description><author>Geography &amp;amp; Coding</author><pubDate>Sat, 18 Nov 2023 14:17:29 GMT</pubDate><guid isPermaLink="true">https://geo.rocks/post/big-data-xml-embeddings/</guid></item><item><title>Checking semver in the presence of doc(hidden) items</title><link>https://predr.ag/blog/checking-semver-for-doc-hidden-items/</link><description>&lt;p&gt;&lt;em&gt;&lt;code&gt;cargo-semver-checks&lt;/code&gt; v0.25 squashes nearly all bugs related to &lt;code&gt;doc(hidden)&lt;/code&gt; items — its most common source of false-positives. What does &lt;code&gt;doc(hidden)&lt;/code&gt; mean in Rust, and why was handling it correctly so hard?&lt;/em&gt;&lt;/p&gt;</description><author>Predrag Gruevski's blog and personal site.</author><pubDate>Sat, 18 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://predr.ag/blog/checking-semver-for-doc-hidden-items/</guid></item><item><title>Stack Overflow tags</title><link>https://vit.baisa.cz/notes/code/stackoverflow-tags/</link><description>tags from StackOveflow with frequency &amp;gt; 10</description><author>Vít Baisa</author><pubDate>Sat, 18 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://vit.baisa.cz/notes/code/stackoverflow-tags/</guid></item><item><title>Exporting Prometheus metrics from HAProxy</title><link>https://ellie.wtf/notes/haproxy-prometheus-metrics/</link><description>&lt;p&gt;I've recently started using haproxy after a lifetime of nginx, and I actually really like it! I needed to setup some metrics, when I discovered that haproxy supports prometheus out of the box - no exporter needed! 🥳&lt;/p&gt;
&lt;p&gt;The documentation shows binding on whatever frontend you're already using, but I wanted to make sure my metrics and stats were not visible to anyone outside of my network&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;listen stats&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;	bind *:9000&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;	mode http&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;	stats enable&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;	stats uri /&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;	http-request use-service prometheus-exporter if { path /metrics }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pretty simple - we're adding a combined frontend/backend, bound on http 9000. The root shows the HAProxy metrics page, and if we hit &lt;code&gt;/metrics&lt;/code&gt; then we show some prometheus metrics. So easy!&lt;/p&gt;</description><author>Ellie's Notes</author><pubDate>Sat, 18 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ellie.wtf/notes/haproxy-prometheus-metrics/</guid></item><item><title>Goals for 2024</title><link>https://chrisfrew.in/blog/goals-for-2024/</link><description>&lt;div style="width: 100%; margin: 0 auto; padding: 40px 40px;"&gt;
                    &lt;p&gt;
                      There's a new blog post &lt;em&gt;"Goals for 2024"&lt;/em&gt; and you can &lt;a href="https://chrisfrew.in/blog/goals-for-2024/"&gt;read it online&lt;/a&gt;.
                      &lt;br /&gt;
                      undefined
                      &lt;br /&gt;
                      You can also &lt;a href="https://chrisfrew.us19.list-manage.com/subscribe/post?u=5f7289fbe97df30f673068826&amp;amp;id=b1729bbdce"&gt;subscribe&lt;/a&gt; for weekly emails on my latest posts - if I publish anything that week!
                    &lt;/p&gt;
                  &lt;/div&gt;</description><author>Chris' Full Stack Blog RSS Feed</author><pubDate>Sat, 18 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://chrisfrew.in/blog/goals-for-2024/</guid></item><item><title>[TIL] nix-darwin and home-manager: Setting up Nix on macOS</title><link>https://davi.sh/til/nix/nix-macos-setup/</link><description>&lt;p&gt;I recently got a new computer and am spending some time actually digging into Nix for managing dependencies. My main issue the last time I tried using Nix was how tough it was for me to wade through the documentation, so I was determined to try a different path this time. I started off with the very opinionated &lt;a href="https://zero-to-nix.com/start/install"&gt;Zero to Nix&lt;/a&gt; tutorial series, which I thought was a great introduction.&lt;/p&gt;
&lt;p&gt;After learning about Nix and Flakes, and getting my website building locally with a nix-powered development environment courtesy of the guidance in &lt;a href="https://zero-to-nix.com/start/init-flake"&gt;Part 6&lt;/a&gt;, the next step was to see what else I could do. I’d heard of two different tools, &lt;a href="https://github.com/nix-community/home-manager"&gt;&lt;code&gt;home-manager&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/LnL7/nix-darwin"&gt;&lt;code&gt;nix-darwin&lt;/code&gt;&lt;/a&gt;. I started off setting up &lt;code&gt;home-manager&lt;/code&gt;, but I was confused on how I should be installing graphical applications like VS Code, and system utilities like Karabiner Elements. It turned out &lt;code&gt;nix-darwin&lt;/code&gt; was the answer there.&lt;/p&gt;
&lt;p&gt;To paraphrase a &lt;a href="https://www.reddit.com/r/NixOS/comments/jznwne/effectively_combining_homemanager_and_nixdarwin/"&gt;Reddit thread I found&lt;/a&gt;, &lt;code&gt;nix-darwin&lt;/code&gt; manages system-level programs and configuration, while &lt;code&gt;home-manager&lt;/code&gt; manages user configuration. You can actually get &lt;code&gt;nix-darwin&lt;/code&gt; to call into &lt;code&gt;home-manager&lt;/code&gt; as part of its own configuration to use both at the same time. This is described in the &lt;a href="https://nix-community.github.io/home-manager/index.html#sec-install-nix-darwin-module"&gt;&lt;code&gt;home-manager&lt;/code&gt; reference docs&lt;/a&gt;, but like many Nix resources, it’s not very simple for a beginner to figure out how to actually put this snippet to use in their own configuration.&lt;/p&gt;
&lt;p&gt;Before following these instructions, you should follow the &lt;a href="https://zero-to-nix.com/start/install"&gt;Zero to Nix&lt;/a&gt; guide or install Nix on your system in some other way.&lt;/p&gt;
&lt;p&gt;I’m going to lay out the basic steps that I followed below while sharing each configuration file.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a nix-darwin flake file. From the &lt;a href="https://github.com/LnL7/nix-darwin/blob/b658dbd85a1c70a15759b470d7b88c0c95f497be/README.md#step-1-creating-flakenix"&gt;README&lt;/a&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="astro-code github-dark" style="background-color: #24292e; color: #e1e4e8;" tabindex="0"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;mkdir&lt;/span&gt;&lt;span style="color: #79B8FF;"&gt; -p&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; ~/.config/nix&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #79B8FF;"&gt;cd&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; ~/.config/nix&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;nix&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; flake&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; init&lt;/span&gt;&lt;span style="color: #79B8FF;"&gt; -t&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; nix-darwin&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;sed&lt;/span&gt;&lt;span style="color: #79B8FF;"&gt; -i&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; ''&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; "s/simple/$(&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;scutil&lt;/span&gt;&lt;span style="color: #79B8FF;"&gt; --get&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; LocalHostName)/"&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; flake.nix&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The default flake puts the configuration inline to the flake.nix. I decided to copy/paste that configuration out into a separate file called &lt;code&gt;darwin.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="astro-code github-dark" style="background-color: #24292e; color: #e1e4e8;" tabindex="0"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;# darwin.nix&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;{ pkgs&lt;/span&gt;&lt;span style="color: #F97583;"&gt;,&lt;/span&gt;&lt;span style="color: #F97583;"&gt; ... &lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;}:&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # List packages installed in system profile. To search by name, run:&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # $ nix-env -qaP | grep wget&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;    environment&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;systemPackages&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;    [ &lt;/span&gt;&lt;span style="color: #FFAB70;"&gt;pkgs&lt;/span&gt;&lt;span style="color: #F97583;"&gt;.&lt;/span&gt;&lt;span style="color: #FFAB70;"&gt;vim&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; ];&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # Auto upgrade nix package and the daemon service.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;    services&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;nix-daemon&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;enable&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #79B8FF;"&gt; true&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;    services&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;karabiner-elements&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;enable&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #79B8FF;"&gt; true&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # nix.package = pkgs.nix;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # Necessary for using flakes on this system.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;    nix&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;settings&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;experimental-features&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; "nix-command flakes"&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # Create /etc/zshrc that loads the nix-darwin environment.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;    programs&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;zsh&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;enable&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #79B8FF;"&gt; true&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;  &lt;/span&gt;&lt;span style="color: #6A737D;"&gt;# default shell on catalina&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # programs.fish.enable = true;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # Used for backwards compatibility, please read the changelog before changing.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # $ darwin-rebuild changelog&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;    system&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;stateVersion&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #79B8FF;"&gt; 4&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # The platform the configuration will be used on.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;    nixpkgs&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;hostPlatform&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; "aarch64-darwin"&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;    users&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;users&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;davish&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;        name&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; "davish"&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;        home&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; "/Users/davish"&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should remember to change your username and home directory to match.&lt;/p&gt;
&lt;ol start="2"&gt;
&lt;li&gt;Modify &lt;code&gt;flake.nix&lt;/code&gt; to work with &lt;code&gt;home-manager&lt;/code&gt;.
The example code can be found &lt;a href="https://nix-community.github.io/home-manager/index.html#sec-flakes-nix-darwin-module"&gt;in the &lt;code&gt;home-manager&lt;/code&gt; docs&lt;/a&gt;. In case that link breaks, here it is:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="astro-code github-dark" style="background-color: #24292e; color: #e1e4e8;" tabindex="0"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;# flake.nix&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;  description&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; "Darwin configuration"&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;  inputs&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;    nixpkgs&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;url&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; "github:nixos/nixpkgs/nixos-unstable"&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;    darwin&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;url&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; "github:lnl7/nix-darwin"&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;    darwin&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;inputs&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;nixpkgs&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;follows&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; "nixpkgs"&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;    home-manager&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;url&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; "github:nix-community/home-manager"&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;    home-manager&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;inputs&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;nixpkgs&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;follows&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; "nixpkgs"&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;  outputs&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; inputs@{ nixpkgs&lt;/span&gt;&lt;span style="color: #F97583;"&gt;,&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; home-manager&lt;/span&gt;&lt;span style="color: #F97583;"&gt;,&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; darwin&lt;/span&gt;&lt;span style="color: #F97583;"&gt;,&lt;/span&gt;&lt;span style="color: #F97583;"&gt; ... &lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;}: {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;    darwinConfigurations&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;      hostname&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #FFAB70;"&gt; darwin&lt;/span&gt;&lt;span style="color: #F97583;"&gt;.&lt;/span&gt;&lt;span style="color: #FFAB70;"&gt;lib&lt;/span&gt;&lt;span style="color: #F97583;"&gt;.&lt;/span&gt;&lt;span style="color: #FFAB70;"&gt;darwinSystem&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;        system&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; "aarch64-darwin"&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;        modules&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #9ECBFF;"&gt;          ./configuration.nix&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #FFAB70;"&gt;          home-manager&lt;/span&gt;&lt;span style="color: #F97583;"&gt;.&lt;/span&gt;&lt;span style="color: #FFAB70;"&gt;darwinModules&lt;/span&gt;&lt;span style="color: #F97583;"&gt;.&lt;/span&gt;&lt;span style="color: #FFAB70;"&gt;home-manager&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;          {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;            home-manager&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;useGlobalPkgs&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #79B8FF;"&gt; true&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;            home-manager&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;useUserPackages&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #79B8FF;"&gt; true&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;            home-manager&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;users&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;jdoe&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #79B8FF;"&gt; import&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; ./home.nix&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;            # Optionally, use home-manager.extraSpecialArgs to pass&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;            # arguments to home.nix&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;          }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;        ];&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;      };&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol start="4"&gt;
&lt;li&gt;Create a &lt;code&gt;home.nix&lt;/code&gt; file. Here is the one that was generated when I tried to set up home-manager on its own. I’ll replicate it here because the comments were useful as a tutorial on its own:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="astro-code github-dark" style="background-color: #24292e; color: #e1e4e8;" tabindex="0"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;# home.nix&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;{ config&lt;/span&gt;&lt;span style="color: #F97583;"&gt;,&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; pkgs&lt;/span&gt;&lt;span style="color: #F97583;"&gt;,&lt;/span&gt;&lt;span style="color: #F97583;"&gt; ... &lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;}:&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;  # Home Manager needs a bit of information about you and the paths it should&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;  # manage.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;  # This value determines the Home Manager release that your configuration is&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;  # compatible with. This helps avoid breakage when a new Home Manager release&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;  # introduces backwards incompatible changes.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;  #&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;  # You should not change this value, even if you update Home Manager. If you do&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;  # want to update the value, then make sure to first check the Home Manager&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;  # release notes.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;  home&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;stateVersion&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; "23.05"&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;; &lt;/span&gt;&lt;span style="color: #6A737D;"&gt;# Please read the comment before changing.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;  # The home.packages option allows you to install Nix packages into your&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;  # environment.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;  home&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;packages&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # # Adds the 'hello' command to your environment. It prints a friendly&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # # "Hello, world!" when run.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # pkgs.hello&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # # It is sometimes useful to fine-tune packages, for example, by applying&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # # overrides. You can do that directly here, just don't forget the&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # # parentheses. Maybe you want to install Nerd Fonts with a limited number of&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # # fonts?&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # (pkgs.nerdfonts.override { fonts = [ "FantasqueSansMono" ]; })&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # # You can also create simple shell scripts directly inside your&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # # configuration. For example, this adds a command 'my-hello' to your&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # # environment:&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # (pkgs.writeShellScriptBin "my-hello" ''&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    #   echo "Hello, ${config.home.username}!"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # '')&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;  ];&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;  # Home Manager is pretty good at managing dotfiles. The primary way to manage&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;  # plain files is through 'home.file'.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;  home&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;file&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # # Building this configuration will create a copy of 'dotfiles/screenrc' in&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # # the Nix store. Activating the configuration will then make '~/.screenrc' a&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # # symlink to the Nix store copy.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # ".screenrc".source = dotfiles/screenrc;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # # You can also set the file content immediately.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # ".gradle/gradle.properties".text = ''&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    #   org.gradle.console=verbose&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    #   org.gradle.daemon.idletimeout=3600000&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # '';&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;  # You can also manage environment variables but you will have to manually&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;  # source&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;  #&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;  #  ~/.nix-profile/etc/profile.d/hm-session-vars.sh&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;  #&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;  # or&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;  #&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;  #  /etc/profiles/per-user/davish/etc/profile.d/hm-session-vars.sh&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;  #&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;  # if you don't want to manage your shell through Home Manager.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;  home&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;sessionVariables&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # EDITOR = "emacs";&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;  # Let Home Manager install and manage itself.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;  programs&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;home-manager&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;enable&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #79B8FF;"&gt; true&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And there you have it! You can install nix-darwin with:&lt;/p&gt;
&lt;pre class="astro-code github-dark" style="background-color: #24292e; color: #e1e4e8;" tabindex="0"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;nix&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; run&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; nix-darwin&lt;/span&gt;&lt;span style="color: #79B8FF;"&gt; --experimental-feature&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; nix-command&lt;/span&gt;&lt;span style="color: #79B8FF;"&gt; --experimental-feature&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; flakes&lt;/span&gt;&lt;span style="color: #79B8FF;"&gt; --&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; switch&lt;/span&gt;&lt;span style="color: #79B8FF;"&gt; --flake&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; ~/.config/nix&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can now run &lt;code&gt;darwin-rebuild switch --flake ~/.config/nix&lt;/code&gt; to rebuild all your configuration whenever you make a change. Another useful link is the list of &lt;a href="https://daiderd.com/nix-darwin/manual/index.html"&gt;nix-darwin configuration options&lt;/a&gt;. I believe this is the best place to go for information on what can be configured with &lt;code&gt;nix-darwin&lt;/code&gt;.&lt;/p&gt;</description><author>Davis Haupt's Blog</author><pubDate>Fri, 17 Nov 2023 19:00:00 GMT</pubDate><guid isPermaLink="true">https://davi.sh/til/nix/nix-macos-setup/</guid></item><item><title>Why you should try accountability coaching</title><link>https://www.ednevsky.blog/p/ive-been-doing-accountability-coaching</link><description>A new practice I've been doing for the last few years</description><author>Ednevsky Blog</author><pubDate>Fri, 17 Nov 2023 18:30:09 GMT</pubDate><guid isPermaLink="true">https://www.ednevsky.blog/p/ive-been-doing-accountability-coaching</guid></item><item><title>Suggested readings week 46/2023</title><link>https://www.stefanocosta.org/suggested-readings-week-46-2023/</link><description>&lt;hr /&gt;&lt;h3 id="digital-equipment-corporation-memories-on-licklider"&gt;&lt;a href="http://memex.org/licklider.pdf?ref=stefanocosta.org" rel="nofollow noreferrer noopener"&gt;Digital Equipment Corporation memories on Licklider&lt;/a&gt;&lt;/h3&gt;&lt;blockquote&gt;This report honors J. C. R. Licklider for his contributions to computer science research and education in this country. We reprint here two of his papers, originally published in the 1960s, which exemplify his ideas about how computers could enhance human problem-solving.&amp;gt;&lt;/blockquote&gt;</description><author>Food for Thought (by Stefano Costa)</author><pubDate>Fri, 17 Nov 2023 15:11:54 GMT</pubDate><guid isPermaLink="true">https://www.stefanocosta.org/suggested-readings-week-46-2023/</guid></item><item><title>Scooter Crash</title><link>https://denvaar.dev/trash/scooter_crash.html</link><description>--</description><author>denvaar's website</author><pubDate>Fri, 17 Nov 2023 05:40:09 GMT</pubDate><guid isPermaLink="true">https://denvaar.dev/trash/scooter_crash.html</guid></item><item><title>Carving My Own Path</title><link>https://martinrue.com/carving-my-own-path/</link><description>It's time to pack a small bag, leave my current world behind and begin overusing the word "nomad".</description><author>Martin Rue</author><pubDate>Fri, 17 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://martinrue.com/carving-my-own-path/</guid></item><item><title>From Terminator To Wezterm</title><link>https://dystroy.org/blog/from-terminator-to-wezterm/</link><description>&lt;p&gt;This post doesn't compare Terminator and Wezterm. This is the help I would have loved to find when starting to try Wezterm coming from Terminator.&lt;/p&gt;</description><author>dystroy|Canop / blog</author><pubDate>Fri, 17 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://dystroy.org/blog/from-terminator-to-wezterm/</guid></item><item><title>Latent optima</title><link>https://atelfo.github.io/2023/11/17/latent-optima.html</link><description>All data is the result of some selection process. The first order manifestation of this is bias: a failure to select a representative sample of the real underlying thing the data is describing. But selection operates at multiple hierarchies. For something to exist such that data can be collected about it, that something must have been created instead of some other thing — it must have survived a selection process. Because of this, any dataset includes latent information about the traits that tend to survive these selection processes. For example, if you counted up all the members of every species on Earth the resulting dataset would give you some information on the types of creatures that tend to do well in Earth’s biosphere. If you then counted how many of those creatures had fins, and how many had wings, you could get partway towards an understanding of how to construct an optimal ‘model’ animal. A mix-and-match book made real. Zooming down to a lower level, Karl Friston (and others) have argued that organisms are embodied statistical models of the states of their environment1. This is a way of thinking about an organism as “a hypothesis of its environment”; in the words of Peter Munz: “The behavior of a fish and the functioning of a theory of water are exactly identical. The fish represents water by its structure and its functioning. Both features define an initial condition (for example, the degree of viscosity of water) which, when spotted or sensed, trigger off a prognosis or behavioral response which, in case of a fish, fails to be falsified. By contrast, a bird does not represent water” In other words, the states that organisms are most likely to be in are the ones they are likely to prefer2. You find organisms where they choose to be. This may just seem like another spin on the familiar process of evolution by natural selection. But I think the selection-eye-view reveals some deeper truth about what is learned by models when they’re trained on real-world datasets. Machine learning models which learn compressed representations of data also naturally end up learning something about the optimal properties of (and selection process acting on) what they are modelling — even if they aren’t provided information about those properties from the outset. Meta’s protein folding model ESMfold illustrates this principle. ESMfold achieved state-of-the-art accuracy on protein structure prediction, even though its language model was trained on protein sequence data alone: the training process involved feeding the model protein sequences with gaps in them, and getting the model to predict amino acids to ‘fill in the blanks’3. Nevertheless, the information about the shape of protein folds was latent in the sequence data. In order to learn better and better predictions to fill the missing amino acids, ESMfold’s language model had to create an internal representation of the structure of the protein and how it folded. This internal language model representation could then be read out by a folding module (which was trained on sequence data) to generate accurate 3D protein structure predictions. Protein folding is a non-random process, and there are ‘best practices’ in how to structure a given protein sequence to achieve a desired structural motif. Evolution has learned these optima and uses them repeatedly, and because they appear frequently, they can be learned. More generally, the set of proteins that exist in the dataset were only able to be sequenced in the first place because they are evolutionary successful. So there is also latent information about the types of sequences and structures that are biologically optimal in the protein sequence dataset. This principle of latent optimality was exploited by researchers who used Meta’s ESM protein models to improve the properties of monoclonal antibodies that were already highly optimised4. The researchers took antibodies already in clinical use — like Regeneron’s COVID antibody — and ran their sequences through the ESM model, tasking the model with predicting where the true amino acid sequence of the antibodies differed most from the model’s predictions. Then, the researchers selectively replaced the amino acids at those divergent sites with the model’s predictions. These substitutions markedly improved the binding affinity, thermostability, and in vitro potency of the antibodies, without a noticeable increase in off-target binding — all without feeding ESM any information but the protein sequence. This suggests that because samples in datasets have themselves undergone a selection process, as long as we are confident that the selection process is producing outcomes we care about (which we can be reasonably certain of in biology), we can use the statistical properties of those datasets as a sort of optimisation function in themselves. Exploration followed by AI-informed selective regression, or annealing, may be a valuable general approach to optimise new designs generally5. One of the challenges of applying machine learning in medicine has historically been that it’s not clear what optimisation function to use. I’ve previously argued that a way around that may be to collect all sorts of data on parameters of biological systems — a detailed description of states — and then use that as an objective function. The evidence from the antibody optimisation experiments using ESM are perhaps a small piece of support in favour of that approach. Biological systems have already undergone billions of years of selection and optimisation — we may as well make use of that latent information. Thanks to Adam Green and David Yang for giving me the idea for this post I wrote about this at length in my post “Intelligence as efficient model building” &amp;#8617; Homeostasis is a manifestation of this drive to return to optimal states &amp;#8617; Jeffrey Chan pointed out that to me on Twitter that I had originally neglected to mention that while the language model portion of ESMfold doesn’t use structure data, ESMfold’s “folding head” is trained on structure data including PDB and 12 million AlphaFold2 predicted structures. The folding head is fed sequence data and the ESM language model’s internal representations of the protein structure as input and outputs the structural predictions (3D coordinates) &amp;#8617; Derek Lowe has a good summary of the paper here &amp;#8617; I wrote about a similar approach in this essay about automating scientific discovery &amp;#8617;</description><author>Alex’s blog</author><pubDate>Fri, 17 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://atelfo.github.io/2023/11/17/latent-optima.html</guid></item><item><title>Using More FreeBSD</title><link>https://skife.org/b4/more-freebsd/</link><description>&lt;p&gt;I do some volunteer sysadmining for a local non-profit, mostly network management and related things. Between there, and my home setup, I have gradually moved from defaulting to Ubuntu back towards FreeBSD over the last year. Nothing wrong with Ubuntu, but I kept finding FreeBSD to simply be a little bit easier and &lt;a href="https://docs.freebsd.org/en/books/handbook/" rel="external"&gt;a lot better documented&lt;/a&gt;. For a dev box, I'll stick with linux—all the tooling optimizes there first, in particular the container ecosystem is too useful to walk away from. For sysadmin'y stuff though, FreeBSD is where it's at. At a minimum, it's &lt;a href="https://docs.freebsd.org/en/books/handbook/config/#configtuning-rcd" rel="external"&gt;rc system&lt;/a&gt; is much easier to work with than systemd :-)&lt;/p&gt;
&lt;p&gt;My general toolkit for running anything is a pair of &lt;a href="https://www.amazon.com/HP-Prodesk-600-G3-Computer/dp/B07RLW1QB8/" rel="external"&gt;refurbished HP Prodesks&lt;/a&gt; sharing a &lt;a href="https://docs.freebsd.org/en/books/handbook/advanced-networking/#carp" rel="external"&gt;CARP virtual interface&lt;/a&gt; using &lt;a href="https://man.freebsd.org/cgi/man.cgi?query=devd&amp;amp;sektion=8&amp;amp;format=html" rel="external"&gt;devd&lt;/a&gt; events if I need to do anything on failover. It's shockingly easy and just works.&lt;/p&gt;
&lt;p&gt;(side note: I tested 14.0-RELEASE on a pair of them and discovered that if a server is hosting a VIP and needs to to &lt;em&gt;use&lt;/em&gt; that virtual IP when it is the backup, say to &lt;a href="https://skife.org/b4/unbound-and-nsd/"&gt;find DNS&lt;/a&gt;, you need to disable &lt;code&gt;net.inet.ip.source_address_validation&lt;/code&gt; in &lt;a href="https://docs.freebsd.org/en/books/handbook/config/index.html#configtuning-sysctl" rel="external"&gt;sysctl&lt;/a&gt;, but then it works fine. I probably shouldn't do this, but I'll sort that out later.)&lt;/p&gt;
&lt;p&gt;(side note 2: With those old HP Prodesks, you need to go into the BIOS and disable UEFI (switch to use "legacy boot" for the FreeBSD install to work). You don't need to for Ubuntu.)&lt;/p&gt;</description><author>Brian's Waste of Time</author><pubDate>Fri, 17 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://skife.org/b4/more-freebsd/</guid></item><item><title>Github CI</title><link>http://pxtl.ca/2023/11/17/github-ci/</link><description>&lt;p&gt;Well, it took longer than I would've liked but I got Github CI working with this
blog! Finally learned how to use Github workflows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Pxtl/Sandra.Snow/tree/master/.github/workflows"&gt;Workflow files are
here&lt;/a&gt;&lt;/p&gt;</description><author>Pxtl.ca</author><pubDate>Fri, 17 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">http://pxtl.ca/2023/11/17/github-ci/</guid></item><item><title>A Day in the Life of a Damn Interesting Guy [1]</title><link>https://lovebloodrhetoric.com/2023/11/16/a-day-in-the-life-of-a-damn-interesting-guy-1/</link><description>2 am: Bill Clinton calls, desperate for advice. What tie to wear for inauguration? Chide him for forgetting five-hour time difference, suggest he wear an Aloha shirt, go back to sleep. [2] 8 am: To work up an appetite for breakfast, swim two laps around Oahu towing an Atlantis submarine by a rope clenched between [&amp;#8230;]</description><author>Love, Blood &amp;amp; Rhetoric</author><pubDate>Fri, 17 Nov 2023 01:32:24 GMT</pubDate><guid isPermaLink="true">https://lovebloodrhetoric.com/2023/11/16/a-day-in-the-life-of-a-damn-interesting-guy-1/</guid></item><item><title>Childhood Bullying III - Things Left Unsaid</title><link>https://callmeo.live/blog/childhood-bullying-iii-things-left-unsaid/</link><description>&lt;p&gt;&lt;a href="../childhood-bullying-ii-aftermath"&gt;Part two&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;When it comes to writing, if I can&amp;rsquo;t keep things concise, I like to keep them entertaining. Unfortunately childhood bullying doesn&amp;rsquo;t lend itself well to either of these goals, much more when it remains such a raw topic to me.&lt;/p&gt;
&lt;p&gt;Whilst I&amp;rsquo;ve already dedicated two posts to the topic, I still have a few things which I either failed to mention, or felt didn&amp;rsquo;t fit in with either post. With it being a few days since I powered through writing those posts, I&amp;rsquo;ve had some much needed distance to calm down. Hopefully it makes for an easier read.&lt;/p&gt;
&lt;p&gt;I will note though, it&amp;rsquo;s important to talk about it. Even if this serves no practical use in the real world, I still feel a need to put it out there.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;In the real world, I struggle with the thought of being known or remembered. As mentioned before, being bullied is a humiliating stain on your existence, and the fact that others must remember what I became because of it&amp;hellip; It hurts. I wish I could hide it, and to erase any knowledge of myself.
I hold a lot of shame. Every time I curled up crying, every angry outburst. Even though I had little in the way of emotional control back then, I&amp;rsquo;m still weighed down with regret.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m scared of contact from anyone who might have known me. I despise using my actual name, and I fear for a future where it ends up online attached to my current self. The option of changing my name via deed poll is a valid option, but I can&amp;rsquo;t begin to think of a different name to go by, let alone change my name on every service I use!&lt;/p&gt;
&lt;p&gt;This has also affected what I do online. I see so many developers and artists put out the most amazing work under their real name, and I&amp;rsquo;m shocked. There&amp;rsquo;s absolutely no way I could ever attach my real name to something I put out there because of my fear of being found – and I forget that it&amp;rsquo;s not normal to feel this way.&lt;/p&gt;
&lt;p&gt;I try not to pine over what ifs, there&amp;rsquo;s no better way to break a man&amp;rsquo;s soul than to show him what he could&amp;rsquo;ve been, but it&amp;rsquo;s hard not to think about. How can&amp;rsquo;t you when you&amp;rsquo;re that anxious about life and you feel inherently wrong about existing?&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I find it hard to deal with media that brings back thoughts about bullying. I refuse to listen to songs on certain albums because it might share the name of someone who made my life hell.&lt;/p&gt;
&lt;p&gt;And despite my love of the Back to the Future series, it&amp;rsquo;s only recently that I noticed why I feel so conflicted watching it as an adult: the whole series is about standing up for yourself. It&amp;rsquo;s hard to watch those films and feel okay afterwards knowing that I never did when I was younger. That I might as well remain a failure because of it.&lt;/p&gt;
&lt;p&gt;There is one film that very briefly touches on the subject of bullying and that&amp;rsquo;s &lt;em&gt;The World&amp;rsquo;s End&lt;/em&gt; (2013), the final film in Edgar Wright&amp;rsquo;s Cornetto Trilogy. Spoilers ahead.&lt;/p&gt;
&lt;p&gt;Eddie Marsan plays the role of Peter Page, a meek bloke who upon returning to his hometown comes across someone who bullied him relentlessly throughout childhood. He&amp;rsquo;s immediately anxious when he notices his old bully, quietly confiding in his friends about it. Much like other aspects of the film, it&amp;rsquo;s sudden, it&amp;rsquo;s raw, and it gets to me.&lt;/p&gt;
&lt;p&gt;Later on in the film, the bully confronts Peter with a smile, apologising for what he did. Peter is immediately petrified by the encounter, but doesn&amp;rsquo;t remotely consider accepting the apology before taking the chance to knock the bully to the floor, letting out all of his anger with each blow to the head – and eventually he devolves into mindless screaming as he smacks the bully senseless with a tree branch.&lt;/p&gt;
&lt;p&gt;Honestly it&amp;rsquo;s hilarious, but the tragedy is that such a reaction is far too real. I know that if I was confronted with someone I used to know, I&amp;rsquo;d freeze as all the feelings would rush back to me. If I lashed out at them I doubt I&amp;rsquo;d come out on top, but I&amp;rsquo;m sure that I&amp;rsquo;d be little more than an incoherent screaming mess.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I wish I could conclude all of this better, but I can&amp;rsquo;t, it&amp;rsquo;s that much of a mess. Even though it&amp;rsquo;s a part of my history, it&amp;rsquo;s made me who I am today, and I have to carry that weight.&lt;/p&gt;</description><author>callmeolive</author><pubDate>Thu, 16 Nov 2023 23:05:00 GMT</pubDate><guid isPermaLink="true">https://callmeo.live/blog/childhood-bullying-iii-things-left-unsaid/</guid></item><item><title>Cuddle Up to KDL</title><link>https://peterlyons.com/problog/2023/11/cuddle-up-to-kdl/</link><description>&lt;p&gt;I've been trying out a relatively new config file format called &lt;a href="https://kdl.dev"&gt;KDL&lt;/a&gt;. It's pronounced "cuddle" and here's a very quick run-down of what it's for and why I find it worth experimenting with. It's a general purpose data language that feels mostly like a blend of JSON, command line, and XML. So it would be useable for use cases including configuration files, data interchange, and data storage, but so far I've only been considering it for the use case of a configuration file intended for manual editing by me primarily.&lt;/p&gt;
&lt;p&gt;Read &lt;a href="https://kdl.dev"&gt;the main kdl.dev&lt;/a&gt; site for a very nice intro and full docs, but to just give you the gist of it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It has nice syntax
&lt;ul&gt;
&lt;li&gt;Nesting with curly braces works fine even for deep trees&lt;/li&gt;
&lt;li&gt;No need to quote every property like JSON&lt;/li&gt;
&lt;li&gt;More terse than JSON without getting into YAML ambiguity and gotchas&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;It supports comments properly&lt;/li&gt;
&lt;li&gt;It has a nice spec, a test suite, and docs around consistent interchange between JSON and KDL&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="don-t-link-to-xkcd-don-t-do-it"&gt;Don't link to xkcd. Don't do it.&lt;/h1&gt;
&lt;p&gt;Yes, I've seen the comic. You've seen the comic. Everyone has seen the comic. There are real and painful issues with JSON, YAML, TOML, XML, and bespoke formats that make exploring new formats interesting and compelling &lt;strong&gt;to me&lt;/strong&gt;. If you are hoping for a world where someone created the one true syntax and everyone just used that and lived happily ever after, I'm sorry to disappoint you. You might be best served by closing this browser tab and moving on at this point.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://kdl.dev/#faq"&gt;KDL FAQ&lt;/a&gt; addresses the common "Why don't you just use language X?" questions.&lt;/p&gt;
&lt;h1 id="using-kdl-in-my-day-to-day"&gt;Using KDL in my day to day&lt;/h1&gt;
&lt;h2 id="rofi-and-launch-lag"&gt;rofi and launch lag&lt;/h2&gt;
&lt;p&gt;I am a huge fan of a utility program called &lt;a href="https://github.com/davatorium/rofi"&gt;rofi&lt;/a&gt; which I use to build many pop-up windows in my desktop environment that are absolutely the core of my workflow: switching programs, running scripts, opening bookmarks, grabbing emoji, etc. I ended up building my own variant because I wanted slightly different behavior (stay running permanently instead of do-then-exit) and I couldn't find a way to do it with the popular rofi project. So I set out to write a basic version very tailored to my needs, which ended up being an interesting journey.&lt;/p&gt;
&lt;h2 id="false-start-in-go"&gt;false start in go&lt;/h2&gt;
&lt;p&gt;I started in go because I love the static binaries and not mucking with cloning source repos, installing interpreters, installing dependencies, etc.&lt;/p&gt;
&lt;p&gt;My config file started as JSON and has a nested tree structure. I quickly realized how annoying this is to handle in go and struggled to model the types nicely as go structs that could be unmarshaled.&lt;/p&gt;
&lt;p&gt;Because I was keen to just get something working to confirm or deny my hypothesis that a custom rofi variant would actually solve my problem, I knew I could do this way faster in node.js. This type of thing is cake in node, so I switched to node using commonjs modules and vanilla javascript.&lt;/p&gt;
&lt;h2 id="node-js-nofi-and-the-elm-architecture"&gt;node.js: nofi and the elm architecture&lt;/h2&gt;
&lt;p&gt;I got that version working well enough pretty quickly. I named the project "nofi". I haven't done much building of true terminal TUI style apps (and it's been mostly bash scripts), so I wasn't sure whether I'd need a terminal library to get some widgets, screen layout stuff, something to deal with the terminal and treat it essentially like a GUI toolkit. I decided pretty early to try a basic &lt;a href="https://guide.elm-lang.org/architecture/"&gt;elm architecture&lt;/a&gt; which at this point has proven itself to be an amazing balance of utility and beauty/simplicity. I just hand coded a very basic &lt;code&gt;update()&lt;/code&gt; &amp;amp; &lt;code&gt;view()&lt;/code&gt; API and keystroke event handling. No library for this although I'm sure many exist, I just coded them up loosely, not worrying to much initially about doing things like &lt;code&gt;console.log&lt;/code&gt; during the &lt;code&gt;update(model, message)&lt;/code&gt; totally in dirty personal project mode.&lt;/p&gt;
&lt;p&gt;I got the thing fully functioning way faster than I expected and it was working and clocking in under 200 lines of code. I added some unit tests since the elm architecture makes that so very easy.&lt;/p&gt;
&lt;h2 id="replacing-json-with-kdl"&gt;replacing JSON with KDL&lt;/h2&gt;
&lt;p&gt;I think it was at exactly this point that I discovered KDL on mastodon and immediately thought "Hmm. I bet this would be nice for nofi so I could add comments". It would also be way easier to quickly add to my config file without too much friction since my use case maps so nicely to the KDL syntax. So I grabbed the js parsing library and ported my config file from JSON to KDL and swapped out &lt;code&gt;JSON.parse()&lt;/code&gt; for the &lt;code&gt;kdl.parse()&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="a-porting-frenzy"&gt;A porting frenzy&lt;/h2&gt;
&lt;p&gt;Because of my current circumstances of keeping my tech skills up to date and job searching, things then got interesting. It went down roughly like this.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I ported nofi to ES modules&lt;/li&gt;
&lt;li&gt;I ported nofi to typescript&lt;/li&gt;
&lt;li&gt;I ported nofi from node to bun&lt;/li&gt;
&lt;li&gt;I ported the unit tests to bun's built-in library&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So now I had a nicer config file syntax that was very concise but not something bespoke and specific to nofi, just a generic syntax I could learn once and use a lot.&lt;/p&gt;
&lt;p&gt;I also now had modeled the data structure in Typescript types, so maybe I could try porting it to go using that as a reference?&lt;/p&gt;
&lt;h2 id="oops-no-go-support"&gt;Oops, no go support&lt;/h2&gt;
&lt;p&gt;At this time in Oct 2023, there were no go implementations of KDL parsing listed on the KDL website. Ah ha! I exclaimed, I will write the first go parser! So I set out to do just that.&lt;/p&gt;
&lt;h2 id="parsing-peg-and-pidgeon"&gt;Parsing, PEG, and pidgeon&lt;/h2&gt;
&lt;p&gt;In my brief research about parsing, lexing, recursive descent, hand-tuned parsers, parser combinators, etc, I stumbled upon the Parsing Expression Grammar (PEG) technology and found a go implementation called &lt;a href="https://github.com/mna/pigeon/tree/master/examples"&gt;mna/pigeon&lt;/a&gt;. So I played around with that. The basic workflow is you model your language in the special PEG syntax, and then codegen builds you a library that can parse it. And the great thing was that KDL already publishes the PEG syntax in their spec (albeit with cosmetic syntax differences which seems to be a nuisance across the whole PEG ecosystem - they are all similar but not identical). So great - I could in theory take that grammar, tweak the syntax to match what pigeon needs, and get a go parser built? Maybe?&lt;/p&gt;
&lt;p&gt;I got a lot of stuff working with this approach. The only other key thing is after the generated code has built an abstract syntax tree, it's basically a sea of &lt;code&gt;[]interface{}&lt;/code&gt; in go and you need to write little snippets of go code manually to map that to nice proper types which you can design. So I modeled the basic elements of KDL - a &lt;code&gt;Document&lt;/code&gt; which is basically a tree of &lt;code&gt;Node&lt;/code&gt;s and nodes have &lt;code&gt;Properties&lt;/code&gt; and &lt;code&gt;Args&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I was able to get through the bulk of KDL by porting a bit of PEG, writing some go snippets, and working my way around the language incrementally. I got a lot of stuff working this way:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;node lists and node identifiers&lt;/li&gt;
&lt;li&gt;key/value properties&lt;/li&gt;
&lt;li&gt;positional arguments&lt;/li&gt;
&lt;li&gt;scalar data types
&lt;ul&gt;
&lt;li&gt;booleans&lt;/li&gt;
&lt;li&gt;numbers&lt;/li&gt;
&lt;li&gt;quoted strings&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;nested nodes&lt;/li&gt;
&lt;li&gt;slashdash comments&lt;/li&gt;
&lt;li&gt;node type annotations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This was my night/weekend hobby project for a while, but as I got all the low hanging fruit working, I started to hit issues building the remaining bits of the PEG grammar that I was unable to debug in go. The generated code and sea of deeply nested untyped &lt;code&gt;[]interface{}&lt;/code&gt; eventually stopped me in my tracks.&lt;/p&gt;
&lt;h2 id="open-source-provides"&gt;Open Source Provides&lt;/h2&gt;
&lt;p&gt;At this point as I was circling back to the KDL docs to see if I missed anything, I just happened to notice that in just the past few weeks, suddenly instead of zero go implementations, there were now two (2!) separate implementations listed on the KDL site. &lt;a href="https://github.com/sblinch/kdl-go"&gt;kdl-go&lt;/a&gt; and &lt;a href="https://github.com/lunjon/gokdl"&gt;gokdl&lt;/a&gt;. It seems I wasn't the only go developer interested, and these folks had more success. They had both written scanners and parsers by hand, which involves a lot of fiddly unicode/rune stuff. One of them claimed to pass the full suite of tests from the KDL spec. So while I still want to get my pigeon version finished, I could try to use one of these libraries to get unblocked on porting nofi to go. This timing has &lt;a href="https://peterlyons.com/problog/2017/12/recurse-center-21-nom-and-combinators/"&gt;happened to me before&lt;/a&gt; (except an open source library for what I needed literally appeared during my subway commute into Recurse Center that time), and it's such an awesome feeling! It makes me want to high-five these people through the Internet.&lt;/p&gt;
&lt;h2 id="gofi-and-bubbletea"&gt;gofi and bubbletea&lt;/h2&gt;
&lt;p&gt;So I added kdl-go and felt some confirmation since the type model almost exactly matched what was in the javascript library as well as what I was working on in my kdlpigeon project.&lt;/p&gt;
&lt;p&gt;OK so I had my data loaded in from my config file, but how should I TUI in go? I was aware that there are great libraries for this in go, and aware of &lt;a href="https://github.com/charmbracelet/bubbletea"&gt;bubbletea&lt;/a&gt; in particular, but oh boy was I pleased to read the docs on bubble tea and discover that it's actually the elm architecture for TUI programs in go. :chef-kiss:.&lt;/p&gt;
&lt;p&gt;I felt immediately at home and blazed through porting my typescript implementation with my home grown elm architecture to go and bubbletea. There was basically no impedance mismatch and everything came over fast and easy. In pretty short order, I had all the basic nuts and bolts of dynamic key mapping across a nested menu structure working in go. I ported over the unit tests and then started using code coverage analysis to add unit tests to cover more paths through the big main &lt;code&gt;switch&lt;/code&gt; statement within the &lt;code&gt;update()&lt;/code&gt; method which is where the sausage is always made in the elm architecture.&lt;/p&gt;
&lt;h2 id="daily-driving-gofi"&gt;Daily driving gofi&lt;/h2&gt;
&lt;p&gt;Here's a screenshot of gofi at the moment.&lt;/p&gt;
&lt;p&gt;&lt;img alt="gofi screenshot" src="https://peterlyons.com/problog/images/2023/gofi.png" /&gt;&lt;/p&gt;
&lt;p&gt;So gofi is now functionally complete and I have updated all my awesomewm keybindings and integrations to use it in my daily workflow. Keep in mind, I run this thing almost every time I switch applications so we're talking for sure hundreds if not thousands of times in a given day. It has to be blazing fast and functionally flawless.&lt;/p&gt;
&lt;p&gt;One technical glitch in debugging TUI code in go is so far I don't know of a good way to launch the debugger from with VS Code and have a proper TTY available and way to send keystrokes. So there's some friction there which requires me to launch a &lt;code&gt;dlv&lt;/code&gt; debugger process from a terminal with a TTY where the TUI can show and I can use it, then attach to that from VS Code and switch back and forth while debugging. It does work though. It's great to have a breakpoint at the beginning of &lt;code&gt;update()&lt;/code&gt; and just examine all the calls there to understand your app state and how bubbletea represents the terminal as messages coming into your program's core logic.&lt;/p&gt;
&lt;h1 id="aside-on-dynamic-and-static-typing"&gt;Aside on dynamic and static typing&lt;/h1&gt;
&lt;p&gt;At the beginning of this project, I was not yet clear on how to structure my configuration file. I didn't know exactly what data shape to use to represent my application. I needed a few cycles of writing a flavor, trying to code against that, realizing some issue, revising the structure to address, and so on. Go being statically typed presents pretty much a hard wall here. Either you have some totally sound representation of all this and you can compile and run your program, or you cannot compile at all. Whereas in dynamic land, I can parse a document into whatever the library gives me and then interactively look at it in the debugger to learn its shape and how bits of syntax in the file get represented within the javascript program. A few sessions later, after I had a working Typescript program, porting it to go was easy and useful, but using dynamic types as a stepping stone was highly useful and effective for me.&lt;/p&gt;
&lt;h1 id="fin"&gt;Fin&lt;/h1&gt;
&lt;p&gt;So go check out &lt;a href="https://kdl.dev"&gt;KDL&lt;/a&gt; and play around with it. I would still like to get closure on my kdlpigeon project by squashing the current bug and then completing the remaining bits of the PEG grammar. I'm curious how it fares compared to the hand-coded state machine scanners and parsers in the other open source libraries.&lt;/p&gt;</description><author>Pete's Points</author><pubDate>Thu, 16 Nov 2023 17:13:25 GMT</pubDate><guid isPermaLink="true">https://peterlyons.com/problog/2023/11/cuddle-up-to-kdl/</guid></item><item><title>Flutter Stuff From Substack This Week</title><link>https://fredgrott.medium.com/flutter-stuff-from-substack-this-week-6a147b6d1e31?source=rss-2cddef1829fb------2</link><description>&lt;div class="medium-feed-item"&gt;&lt;p class="medium-feed-image"&gt;&lt;a href="https://fredgrott.medium.com/flutter-stuff-from-substack-this-week-6a147b6d1e31?source=rss-2cddef1829fb------2"&gt;&lt;img src="https://cdn-images-1.medium.com/max/1080/1*a8cEixOIhNLyww1CEz3-dA.png" width="1080" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p class="medium-feed-snippet"&gt;Sort of the weekly reminder that I moved my Flutter posts to substack.&lt;/p&gt;&lt;p class="medium-feed-link"&gt;&lt;a href="https://fredgrott.medium.com/flutter-stuff-from-substack-this-week-6a147b6d1e31?source=rss-2cddef1829fb------2"&gt;Continue reading on Medium »&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;</description><author>Stories by Fred Grott on Medium</author><pubDate>Thu, 16 Nov 2023 13:07:55 GMT</pubDate><guid isPermaLink="true">https://fredgrott.medium.com/flutter-stuff-from-substack-this-week-6a147b6d1e31?source=rss-2cddef1829fb------2</guid></item><item><title>sum-grid.love</title><link>http://akkartik.name/post/sum-grid</link><description>&lt;p&gt;
&lt;a href="https://git.sr.ht/~akkartik/sum-grid.love"&gt;A little sudoku-like app
for helping first-graders practice addition.&lt;/a&gt; This attempt at situated
software for schooling got a little more use than &lt;a href="https://akkartik.name/post/spell-cards"&gt;spell-cards.love&lt;/a&gt;.

&lt;p&gt;
&lt;a href="https://akkartik.name/images/20231116-sum-grid-fixed.webm"&gt;
  &lt;video style="width: 80%; margin-left: 10%; border: 2px solid #aaaaff;"&gt;
    &lt;source src="https://akkartik.name/images/20231116-sum-grid-fixed.webm" /&gt;
  &lt;/video&gt;
&lt;/a&gt;
&lt;div class="btw" style="text-align: right;"&gt;
  video; 25 seconds
&lt;/div&gt;</description><author>Kartik Agaram</author><pubDate>Thu, 16 Nov 2023 09:00:00 GMT</pubDate><guid isPermaLink="true">http://akkartik.name/post/sum-grid</guid></item><item><title>SANS Recommendations for Defensive Cyber Analysts</title><link>https://zacs.site/blog/sans-recommendations.html</link><description>&lt;p&gt;I keep fielding this question in private, so I finally decided to answer it in public: &amp;#8220;What SANS courses should I take?&amp;#8221; Although I have a much longer answer to give about training in general, this article answers that specific question based on my own personal experience, having taken several SANS courses over the years.&lt;/p&gt;
                &lt;p&gt;&lt;a href="https://zacs.site/blog/sans-recommendations.html"&gt;Permalink.&lt;/a&gt;&lt;/p&gt;</description><author>Zac Szewczyk</author><pubDate>Thu, 16 Nov 2023 07:38:05 GMT</pubDate><guid isPermaLink="true">https://zacs.site/blog/sans-recommendations.html</guid></item><item><title>Dissecting UPI: a DPI by NPCI which we cannot RTI</title><link>https://www.cpuonfire.com/blog/upi/</link><description>Phew, that&amp;rsquo;s a lot of Is. The terms like Digital Public Infrastructure (DPI) and Digital Public Goods (DPG) have been used liberally at the G20 summit, and UPI is India&amp;rsquo;s poster child for all of this. This post will be a summary of a rabbit hole I dug about UPI, NPCI, and other things.
NPCI is the National Payments Corporation of India which is legally a non-profit controlled and funded by the RBI and a bunch of (mostly public) banks.</description><author>cpuonfire</author><pubDate>Thu, 16 Nov 2023 06:18:46 GMT</pubDate><guid isPermaLink="true">https://www.cpuonfire.com/blog/upi/</guid></item><item><title>Acquiring the skills of growth</title><link>https://bowendwelle.substack.com/p/acquiring-the-skills-of-growth</link><description>Change can seem entirely impossible&amp;#8212;and, it can be normal, and easy.</description><author>An Ordinary Disaster</author><pubDate>Thu, 16 Nov 2023 04:17:49 GMT</pubDate><guid isPermaLink="true">https://bowendwelle.substack.com/p/acquiring-the-skills-of-growth</guid></item><item><title>Migrating/re-encrypting pass's password store</title><link>https://xenodium.com/migratingre-encrypting-passs-password-store</link><description>&lt;p&gt;Note to self, I needed to migrate/re-encrypt someone's password store (aka pass). Straightforward:&lt;/p&gt;
&lt;p&gt;Get the new key, probably already in gpg key chain. Try listing it:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.bash"&gt;gpg --list-keys
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To re-encrypt, &lt;code&gt;pass init&lt;/code&gt; with new key is enough. It'll prompt for old pass key.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.bash"&gt;cd path/to/.password-store
pass init NEW-GPG-KEY
&lt;/code&gt;&lt;/pre&gt;</description><author>xenodium.com @alvaro</author><pubDate>Thu, 16 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/migratingre-encrypting-passs-password-store</guid></item><item><title>Unicorn coloring page with broomstick and Halloween theme</title><link>https://studiofreya.org/posts/unicorn-coloring-page-with-broomstick-and-halloween-theme/</link><description>&lt;p&gt;Generated by GPT-4 with prompt:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Can you create a coloring page of a cute, sitting unicorn with wings and large eyes, with a broom ready to fly, with a halloween theme?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;GPT-4 description:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A black and white Halloween-themed coloring page featuring a cute, sitting unicorn with wings, large expressive eyes, and holding a broom, ready to fly. The unicorn should have a flowing mane and tail, and a spiraled horn. Its wings are detailed and feathered. The Halloween theme is reflected in the unicorn&amp;rsquo;s playful costume elements like a witch&amp;rsquo;s hat, and Halloween decorations like pumpkins and bats around it. The background should be simple, enhancing the whimsical, festive atmosphere, perfect for a children&amp;rsquo;s Halloween coloring book.&lt;/p&gt;
&lt;/blockquote&gt;</description><author>Studiofreya SSG Site</author><pubDate>Thu, 16 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://studiofreya.org/posts/unicorn-coloring-page-with-broomstick-and-halloween-theme/</guid></item><item><title>Visual Assist Open Corresponding File</title><link>https://studiofreya.org/posts/visual-assist-open-corresponding-file/</link><description>&lt;p&gt;Visual Studio got updated to 17.8, and the shortcut &lt;code&gt;Alt-O&lt;/code&gt; to open corresponding file in Visual Assist was unassigned &lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Open Visual Studio options, and reassign the &lt;code&gt;VAssistX.OpenCorrespondingFile&lt;/code&gt; command to &lt;code&gt;Alt-O&lt;/code&gt;.&lt;/p&gt;</description><author>Studiofreya SSG Site</author><pubDate>Thu, 16 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://studiofreya.org/posts/visual-assist-open-corresponding-file/</guid></item><item><title>ChatGPT Killed my Blog</title><link>https://backendhance.com/blog/2023/chatgpt-killed-my-blog/</link><description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;p&gt;Meine Arbeitsweise hat sich im letzten Jahr so stark geändert wie nie zuvor.&lt;/p&gt;
&lt;p&gt;Vor einem Jahr hat OpenAI ChatGPT veröffentlicht.&lt;/p&gt;
&lt;p&gt;Damals fand ich die Technologie schon cool, aber ich habe nicht erkannt, wie groß der Einfluss auf meine Arbeit sein wird. Und das in nicht mal 12 Monaten.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Heute nutze ich KI für so viele Dinge:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ich google keine Fragen mehr, es wird ChatGPT gefragt&lt;/li&gt;
&lt;li&gt;Github Copilot schreibt Code bevor ich weiß was ich überhaupt schreiben wollte&lt;/li&gt;
&lt;li&gt;Mein Content (auch dieser) wird von ChatGPT lektoriert&lt;/li&gt;
&lt;li&gt;Stock Images lasse ich mir von Midjourney generieren&lt;/li&gt;
&lt;li&gt;Meine Vorträge der letzten Wochen sind mit ChatGPT entstanden&lt;/li&gt;
&lt;li&gt;Selbst das Piraten-Rätsel vom Kindergeburtstag generiert ChatGPT&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Und diese Liste ist bei weitem nicht vollständig.&lt;/p&gt;</description><author>Backendhance</author><pubDate>Thu, 16 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/chatgpt-killed-my-blog/</guid></item><item><title>Introducing Wikid: A(nother) Wiki System</title><link>https://one.mikro2nd.net/pages/wikid-intro/</link><description>I&amp;rsquo;m writing a wiki system. Have I lost my mind? Why, when there are already so many wiki platforms languishing out there&amp;hellip;?
First I have been looking for a good wiki platform to use for collaboration (I have an actual use-case with real people and genuine need), and not finding many I&amp;rsquo;d rate as suitable. Quite a number of the platforms out there are just way too complex, lacking the sense of elegance and WikiNature I expect and want out of a wiki system.</description><author>one mikro2nd</author><pubDate>Thu, 16 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://one.mikro2nd.net/pages/wikid-intro/</guid></item><item><title>Buildpacks - 3 months later</title><link>https://domenicoluciani.com/2023/11/16/buildpacks-3-months-later.html</link><description>Since the beginning of my career in this industry, I've always been fascinated by the open-source world. After so many years of contributing on my own, this year, I finally joined…</description><author>Domenico Luciani</author><pubDate>Thu, 16 Nov 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://domenicoluciani.com/2023/11/16/buildpacks-3-months-later.html</guid></item><item><title>Tools for working with JSON</title><link>https://gaganpreet.in/posts/json-tools/</link><description>&lt;p&gt;I often work with JSON responses and need to process, view or compare objects. I have come to depend on a bunch of command line tools for these tasks. Do you use any other tools? Let me know in comments below.&lt;/p&gt;
&lt;h2 class="relative group" id="view-bat"&gt;View (bat) &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" href="#view-bat"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://github.com/sharkdp/bat" rel="noreferrer" target="_blank"&gt;&lt;code&gt;bat&lt;/code&gt;&lt;/a&gt; (better cousin of cat) is perfect for reading syntax highlighted JSON. It&amp;rsquo;ll use a pager for files that don&amp;rsquo;t fit on the screen and files can be searched by using the &lt;code&gt;/&lt;/code&gt; operator.&lt;/p&gt;</description><author>Gaganpreet Arora</author><pubDate>Thu, 16 Nov 2023 00:14:52 GMT</pubDate><guid isPermaLink="true">https://gaganpreet.in/posts/json-tools/</guid></item><item><title>CloudFront and the Wild West of the Internet</title><link>https://tig.pt/cloudfront-and-the-wild-west-of-the-internet/</link><description>&lt;p&gt;The internet is truly amazing, but it is a Wild West.&lt;/p&gt;&lt;p&gt;The other day I was looking at my CloudFront &lt;code&gt;Popular objects&lt;/code&gt; and was supper curious, this is pointing to an S3 bucket with some static images and &lt;a href="https://tig.pt/python-amazon-polly/"&gt;polly mp3&lt;/a&gt; that are serving the mobile app.&lt;/p&gt;&lt;p&gt;What &lt;code&gt;wp-login.php&lt;/code&gt;&lt;/p&gt;</description><author>Tiago Rodrigues</author><pubDate>Thu, 16 Nov 2023 00:10:00 GMT</pubDate><guid isPermaLink="true">https://tig.pt/cloudfront-and-the-wild-west-of-the-internet/</guid></item><item><title>A Crash Course on Music Theory</title><link>https://epiccoleman.com/posts/2023-11-15-music-theory-crash-course.html</link><description>A crash course on music theory that I wrote to help a Reddit user understand a mysterious diagram their guitar teacher drew.</description><author>epiccoleman.com</author><pubDate>Wed, 15 Nov 2023 22:33:34 GMT</pubDate><guid isPermaLink="true">https://epiccoleman.com/posts/2023-11-15-music-theory-crash-course.html</guid></item><item><title>how to rename files without specifying the path twice</title><link>https://evilcookie.de/how-to-rename-files-without-specifying-the-path-twice.html</link><description/><author>blog</author><pubDate>Wed, 15 Nov 2023 19:26:47 GMT</pubDate><guid isPermaLink="true">https://evilcookie.de/how-to-rename-files-without-specifying-the-path-twice.html</guid></item><item><title>rustcred, a git credentials helper</title><link>https://rjp.is/blogging/posts/2023/11/rustcred/</link><description>In which we store some credentials with example code.</description><author>infrequent oscillations</author><pubDate>Wed, 15 Nov 2023 15:54:31 GMT</pubDate><guid isPermaLink="true">https://rjp.is/blogging/posts/2023/11/rustcred/</guid></item><item><title>How to use Weblate</title><link>https://river.me/blog/weblate/</link><description>A guide to using Weblate as a translator</description><author>River Writes - A MediaWiki Blog</author><pubDate>Wed, 15 Nov 2023 04:34:59 GMT</pubDate><guid isPermaLink="true">https://river.me/blog/weblate/</guid></item><item><title>📷 A Photo That Means the World to Me</title><link>https://james-carr.org/posts/a-photo-that-means-the-world-to-me/</link><description>10 years ago, as I was getting ready to take kids somewhere I heard them both laughing wildly in the backseat. I snapped this photo as quick as I could and to me, it just completely captures the companionship these two siblings have shared since the very beginning. The big grins on both of their faces is something I always hope to see every single day.</description><author>James Carr</author><pubDate>Wed, 15 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://james-carr.org/posts/a-photo-that-means-the-world-to-me/</guid></item><item><title>Wellness challenge halfway point</title><link>https://benovermyer.com/blog/2023/11/wellness-challenge-halfway-point/</link><description>&lt;p&gt;Here we are at November 15, halfway through the wellness challenge that I set for
myself this month. I've kept to it, and while progress has been slow, something
interesting is happening.&lt;/p&gt;
&lt;p&gt;I started this challenge at 226 lbs. I'm now down to 221. Also, I'm now past the
"warmup" workout plan and into the first week of the "phase 1" workout plan. This
means I'm going to the gym four days a week instead of three, and alternating between
two workout regimens.&lt;/p&gt;
&lt;p&gt;I've settled into a routine now. Going to the gym in the morning is something to
look forward to, rather than a chore to accomplish. That was unexpected.&lt;/p&gt;
&lt;p&gt;I may extend the workouts beyond the end of November. This is turning out to be
awesome.&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Wed, 15 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2023/11/wellness-challenge-halfway-point/</guid></item><item><title>A Kind Introduction</title><link>https://dystroy.org/blog/a-kind-introduction/</link><description>&lt;p&gt;How to leverage the Rust compiler to prevent identifier misuse ?&lt;/p&gt;
&lt;p&gt;How to go from &lt;code&gt;ec2ba151-7acf-43a9-bb98-6f5331992f42&lt;/code&gt; in your database to &lt;code&gt;"Cust_ec2ba151-7acf-43a9-bb98-6f5331992f42"&lt;/code&gt; in your REST/JSON API ?&lt;/p&gt;
&lt;p&gt;How to do it at zero runtime cost, with no boilerplate, with a derive macro ?&lt;/p&gt;</description><author>dystroy|Canop / blog</author><pubDate>Wed, 15 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://dystroy.org/blog/a-kind-introduction/</guid></item><item><title>CHAOS;HEAD NOAH review</title><link>https://burakku.com/blog/chaos-head-noah-review/</link><description>&lt;p&gt;&lt;img alt="CHAOS;HEAD NOAH" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Press any key for schizophrenia.&lt;/p&gt;
&lt;p&gt;CHAOS;HEAD is a really interesting story. On the surface it appears to be a murder mystery story but it's not really that. There are murders and mysteries, but they don't meet in the same way that they would in a whodunit. I guess I'd rather call it a psychological horror, even if it's not really outright horror. And much like STEINS;GATE, the entry in the series that people are probably the most familiar with, it is rife with conspiracies and larger implications. Really enjoyed myself as I read through it for the first ending.&lt;/p&gt;
&lt;p&gt;The meat of the story is really in the common route, which then unlocks the other routes and endings after having completed it once. The character-specific routes aren't that special even if they do flesh out some of the characters a bit more and have some good elements. The most outstanding one of them is the Rimi route, which gives the most exposition for the common route, although I did also enjoy the Sena one too as a Sena fan. The endings also felt a bit lacking compared to the emotions I felt from the common route. Even the true ending didn't really change things up that much or be more detailed than the base ending. Thankfully the common route was interesting so I don't feel soured by these issues.&lt;/p&gt;
&lt;p&gt;The protagonist Takumi is a flawed but interesting character. An anti-social, delusional otaku hiding from the world in his shipping container home. And because of those delusions, he becomes an unreliable narrator for the story, making the reader unsure what parts of the story are real and what are delusions. He enters a delusion, then something unexpected happens and you can't know if it's still a delusion or not. Or he encounters a stranger that supposedly knows him and you can't know which is true. The unreliability of his delusions really fits the whole mysterious and disturbing theme of the story very well.&lt;/p&gt;
&lt;p&gt;The delusion triggers are pretty interesting system that allow having a positive delusion, a negative delusion, or no delusion. The delusions will then trigger either a small scene with no relevance to the plot, or change the actual routing (at least if you answer a quiz correctly). While the game does have an explanation as to why this would happen, it's still pretty hard to figure out why having a negative delusion in a given scenario would result in a different ending. Not quite as opaque of a routing system as the text message system in STEINS;GATE was, but still mixed up a bit. You'll probably want to look up a route guide for this one.&lt;/p&gt;
&lt;p&gt;Art in CHAOS;HEAD is just about alright. Some of the character sprites and CG images have a derpy feeling to them, with the characters looking like their facial features are trying to escape their faces. I didn't find the art fundamentally flawed but it did definitely stick out, even in some quite pivotal parts of the story. The characters also have animated mouths but there's not much animation beyond that and some camera pans. And while the CG did have some quality issues, the amount of it was just fine. I guess this isn't really the most visual of packages.&lt;/p&gt;
&lt;p&gt;But what was actually superb was the sound design. The sound design in this game is fantastic. Takumi is fully voiced and Yoshino does a superb job at voicing his uncertainties, fears, delusions, paranoia and other ailments. Maybe his performance was actually too good, since it overshadowed some of the side characters, even though there were also good performances there. Like for example Takahashi Chiaki's performance as Yua. The music and sound effects are also to the point when it comes to producing a creepy, supernatural and mysterious atmosphere. There's very few times when I'm impressed to this point by just the sound design in a visual novel. You'll definitely want to have audio on for this experience. Bonus points for an excellent insert song.&lt;/p&gt;
&lt;p&gt;You’ll also want to have audio on because it’s very hard to figure out who’s talking without it. The user interface has no indication on who the speaker at the moment is, or if the text is just narration. If you’re deaf, you can go get fucked as far as CHAOS;HEAD is concerned. The interface is also kinda ugly in general and the text display is kinda rough. This is probably a function of the game’s age, as it’s already 15 years old. Not a dealbreaker but something I did definitely note during it.&lt;/p&gt;
&lt;p&gt;For what it's worth, installing the Committee of Zero patch does clean up the text display so maybe consider doing that.&lt;/p&gt;
&lt;p&gt;Whilst it's largely overshadowed by its excellent younger sibling STEINS;GATE, CHAOS;HEAD is still an interesting story and recommended reading despite some of its flaws.&lt;/p&gt;
&lt;p&gt;Maybe I should now check why everyone hates the anime adaptation…&lt;/p&gt;</description><author>ブラック</author><pubDate>Wed, 15 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/chaos-head-noah-review/</guid></item><item><title>What the NFL Taught Me About Business</title><link>https://olivergilan.com/what-the-nfl-taught-me-about-business/</link><description>The NFL acts as a microcosm for business and can teach us lessons about building successful organizations from small startups to large companies. Understanding these lessons will shape the choices and effectiveness of both leadership and ICs and can radically alter the outcomes of a business.</description><author>Oliver Gilan</author><pubDate>Wed, 15 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://olivergilan.com/what-the-nfl-taught-me-about-business/</guid></item><item><title>Moving to AI safety</title><link>https://www.georgeyw.com/moving-to-ai-safety/</link><description>Kicked up some dust this year, some was settled</description><author>This Is My True Name</author><pubDate>Tue, 14 Nov 2023 18:44:00 GMT</pubDate><guid isPermaLink="true">https://www.georgeyw.com/moving-to-ai-safety/</guid></item><item><title>Can Debezium Lose Events?</title><link>https://www.morling.dev/blog/can-debezium-lose-events/</link><description>&lt;div class="paragraph"&gt;
&lt;p&gt;This question came up on the Data Engineering sub-reddit the other day:
&lt;a href="https://old.reddit.com/r/dataengineering/comments/17ttw5e/can_debezium_loose_updates/"&gt;Can Debezium lose any events&lt;/a&gt;?
I.e. can there be a situation where a record in a database get inserted, updated, or deleted, but Debezium fails to capture that event from the transaction log and propagate it to downstream consumers?&lt;/p&gt;
&lt;/div&gt;</description><author>Gunnar Morling</author><pubDate>Tue, 14 Nov 2023 16:00:00 GMT</pubDate><guid isPermaLink="true">https://www.morling.dev/blog/can-debezium-lose-events/</guid></item><item><title>TypeScript source code generation</title><link>https://blog.whilenot.dev/posts/typescript-source-code-generation/</link><description>Examples of different methods to generate valid TypeScript source code via metaprogramming It&amp;rsquo;s about time for another blog post&amp;hellip; been a while! While I still owe it to myself to write the 2nd part of the previous blog post (does anybody have tips for shitty bugs?), I thought it would be time to write about a different topic - Metaprogramming as programming technique.
Sometimes writing source code can get a bit repetitive.</description><author>wh!le / blog</author><pubDate>Tue, 14 Nov 2023 15:33:56 GMT</pubDate><guid isPermaLink="true">https://blog.whilenot.dev/posts/typescript-source-code-generation/</guid></item><item><title>My memorable OpenStreetMap edits</title><link>https://www.unsungnovelty.org/posts/11/2023/my-memorable-openstreetmap-edits/</link><description>&lt;p&gt;OpenStreetMap (OSM) is fun and fantastic for so many reasons. One being able to roam around the world from the comfort of your chair. I started &lt;a href="https://www.openstreetmap.org/user/unsungNovelty"&gt;mapping on OpenStreetMap&lt;/a&gt; in 2015 by coincidence through a friend. And OSM later became part of my work at Mapbox. Where I went through out the world as an armchair mapper. I used to maintain a list of interesting places as I encountered them. Then map it later when I am free. Here are my 3 memorable OpenStreetMap edits.&lt;/p&gt;
&lt;h2 id="valparai-tamil-nadu-india"&gt;Valparai, Tamil Nadu, India&lt;/h2&gt;
&lt;p&gt;&lt;figure&gt;&lt;img alt="Beautiful image of tea plantations in Valparai sprinkled by big trees in between the tea plants" src="https://www.unsungnovelty.org/images/post-images/memorable-osm-edits/Tea_Plantations_in_Valparai,_Tamil_Nadu.jpg" /&gt;
&lt;/figure&gt;

&lt;p class="text-center italic"&gt;Tea plantations in Valparai by Thangaraj Kumaravel&lt;/p&gt;
Image license: &lt;a href="https://creativecommons.org/licenses/by/2.0/deed.en"&gt;CC BY 2.0 Deed&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I have been to Valparai several times as a kid during summer vacations. It&amp;rsquo;s surreal and beautiful. My uncle&amp;rsquo;s place there used to be at the top of the hill, above the clouds itself. You won&amp;rsquo;t be able to see the road when it is cloudy. We used to play hide and seek in these gigantic guava trees. Or maybe they felt gigantic as I was a kid then. Trees were our targets because leopards were regular visitors and we were not &lt;em&gt;&amp;ldquo;technically&amp;rdquo;&lt;/em&gt; allowed to go out of house plot.&lt;/p&gt;
&lt;p&gt;Valparai hence was one of the places I always wanted to map due to cherished memories. Which is what I did. I have put in hours here to map detailed map features. Rekindling the memories while doing so were worth the time I have put into adding data here.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img alt="Screenshot of Valparai in openstreetmap.org" src="https://www.unsungnovelty.org/images/post-images/memorable-osm-edits/valparai-osm.png" /&gt;
&lt;/figure&gt;

&lt;p class="text-center italic"&gt;Screenshot of Valparai in openstreetmap.org&lt;/p&gt;&lt;/p&gt;
&lt;h3 id="my-edit"&gt;My edit&lt;/h3&gt;
&lt;p&gt;&lt;figure&gt;&lt;img alt="Screenshot of my edits on Valparai in openstreetmap.org" src="https://www.unsungnovelty.org/images/post-images/memorable-osm-edits/valparai-osm-my-edits.png" /&gt;
&lt;/figure&gt;

&lt;p class="text-center italic"&gt;Screenshot of my edits on Valparai in openstreetmap.org&lt;/p&gt;&lt;/p&gt;
&lt;h2 id="pettineo-sicilia-italy"&gt;Pettineo, Sicilia, Italy&lt;/h2&gt;
&lt;p&gt;&lt;figure&gt;&lt;img alt="Image of pettineo in Wikipedia" src="https://www.unsungnovelty.org/images/post-images/memorable-osm-edits/pettineo-wiki-image.png" /&gt;
&lt;/figure&gt;

&lt;p class="text-center italic"&gt;Image of pettineo in Wikipedia&lt;/p&gt;
Image details: © &lt;a href="https://commons.wikimedia.org/wiki/User:LigaDue"&gt;LigaDue&lt;/a&gt; | License: &lt;a href="https://creativecommons.org/licenses/by-sa/3.0/deed.en"&gt;CC BY-SA 3.0 Deed&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I came across Pettineo, Sicilia thanks to end user feedback from Mapbox maps. Amazing place. At least from the satellite imagery, it was. To my surprise, it was not mapped at all when I looked at it in JOSM. The edit I did here was far far more grander than Valparai. Or that&amp;rsquo;s how I remember it. What I didn&amp;rsquo;t know was that JOSM somehow hadn&amp;rsquo;t loaded the data of the place. Almost 4ish hours or so of my time resulting in duplicated data as I realised I have mapped over the original data. My edit was extremely detailed over the original data as I recall it. But it did not connected to any data that was already present. Merging felt far more complex and I had to revert my edit.&lt;/p&gt;
&lt;p&gt;After the initial panic and changeset revert, I did edit Pettineo again, it was not as large as my first edit since I didn&amp;rsquo;t have to map half of it. I was just relieved that I was able to fix it as soon as I messed up.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img alt="Screenshot of pettineo in openstreetmap.org" src="https://www.unsungnovelty.org/images/post-images/memorable-osm-edits/pettineo-osm.png" /&gt;
&lt;/figure&gt;

&lt;p class="text-center italic"&gt;Screenshot of Pettineo in openstreetmap.org&lt;/p&gt;&lt;/p&gt;
&lt;h3 id="my-edit-1"&gt;My edit&lt;/h3&gt;
&lt;p&gt;&lt;figure&gt;&lt;img alt="Screenshot of my edits on Pettineo in openstreetmap.org" src="https://www.unsungnovelty.org/images/post-images/memorable-osm-edits/pettineo-osm-my-edits.png" /&gt;
&lt;/figure&gt;

&lt;p class="text-center italic"&gt;Screenshot of my edits on Pettineo in openstreetmap.org&lt;/p&gt;&lt;/p&gt;
&lt;h2 id="dierks-lake-arkansas"&gt;Dierks Lake, Arkansas&lt;/h2&gt;
&lt;p&gt;&lt;figure&gt;&lt;img src="https://www.unsungnovelty.org/images/post-images/memorable-osm-edits/dierks-lake-recreationgov.png" /&gt;
&lt;/figure&gt;

&lt;p class="text-center italic"&gt;Dierks Lake, Arkansas, USA&lt;/p&gt; Image details: © &lt;a href="recreation.gov/camping/campgrounds/10050271"&gt;Recreation.gov&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Over the years, I have created a huge interest in natural features. Especially water bodies. It started with one of my first few edits where I added some of the swimming pools in Delhi which hosted most of the foreign embassies in India.&lt;/p&gt;
&lt;p&gt;Dierks Lake, Arkansas came through work as well. It had only extremely bare geometry. Which I hate in a waterbody. Or anything natural. Whether it is a waterbody or a tree, they have distinct shape and geometry which should be celebrated. Editing the entire lake was around ~2ish hours of work IIRC. Dierks Lake&amp;rsquo;s mapping was the reason I became a fan of waterbody mapping. And hence has a sweet spot in my heart.&lt;/p&gt;
&lt;h3 id="my-edithttpswwwopenstreetmaporgchangeset43332882map13341767-940974"&gt;&lt;a href="https://www.openstreetmap.org/changeset/43332882#map=13/34.1767/-94.0974"&gt;My edit&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure&gt;&lt;img src="https://www.unsungnovelty.org/images/post-images/memorable-osm-edits/dierks-lake-osm-my-edit.png" /&gt;
&lt;/figure&gt;

&lt;p class="text-center italic"&gt;My edit on Dierks Lake, Arkansas, USA&lt;/p&gt;&lt;/p&gt;
&lt;h2 id="whats-next"&gt;What&amp;rsquo;s next?&lt;/h2&gt;
&lt;p&gt;Once you have maps in your life, you will see it everywhere. Apart from some of the map related projects I have been working on, I wanna restart mapping in OSM. Especially natural features like forests, water bodies etc. Here is to creating more map memories.&lt;/p&gt;</description><author>unsungNovelty</author><pubDate>Tue, 14 Nov 2023 11:51:49 GMT</pubDate><guid isPermaLink="true">https://www.unsungnovelty.org/posts/11/2023/my-memorable-openstreetmap-edits/</guid></item><item><title>[Hμblog] Stop deploying web application firewalls</title><link>https://www.devever.net/~hl/u/202311#14a</link><description>&lt;ul&gt;&lt;li&gt;&lt;a href="https://www.macchaffee.com/blog/2023/wafs/"&gt;Stop deploying web application firewalls&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Excellent writeup by &lt;a href="https://www.macchaffee.com/"&gt;Mac Chaffee&lt;/a&gt; about how web application firewalls are universally a terrible idea. I've been meaning to write an article on this subject for some time, but this is a good overview of many issues with them.&lt;/p&gt;</description><author>devever.net/~hl</author><pubDate>Tue, 14 Nov 2023 09:25:59 GMT</pubDate><guid isPermaLink="true">https://www.devever.net/~hl/u/202311#14a</guid></item><item><title>Fun with Makefiles - Dynamic Menu Generation</title><link>https://smcleod.net/2023/11/fun-with-makefiles-dynamic-menu-generation/</link><description>This reusable snippet will generate a menu of targets from the Makefile. It will use `fzf` if it is installed, otherwise it will use a numbered menu.</description><author>smcleod.net</author><pubDate>Tue, 14 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2023/11/fun-with-makefiles-dynamic-menu-generation/</guid></item><item><title>Pseudoprose</title><link>https://taylor.town/pseudoprose</link><description>Pseudoprose is pseudocode for writers.</description><author>taylor.town</author><pubDate>Tue, 14 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/pseudoprose</guid></item><item><title>The least controversial movie</title><link>https://alexmolas.com/2023/11/14/controversial-movies.html</link><description>tldr</description><author>Alex Molas Blog</author><pubDate>Tue, 14 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://alexmolas.com/2023/11/14/controversial-movies.html</guid></item><item><title>📦 Multiple Git Configurations and Identities with Folder-Dependent Includes for GitLab, GitHub et al</title><link>https://jdsalaro.com/tutorial/git-configuration-folder-dependent-conditional-includes/</link><description>People, shout out to some of my friends and collegues, like to configure their development environments in a myriad of ways. Even setting one’s Git author identity, a seemingly boring and mundane task, is sometimes approached in the most creative ways. This, of course, includes not only configuring git, the tool itself, but also any relevant repositories as well as the platforms where those eventually end up hosted. In this brief tutorial I show how to properly handle multiple identities and configurations as well as how to manage Git projects which might be spread throughout multiple Git backends.Please beware this tutorial will likely only be relevant or interesting to you if you already have some experience with Git, otherwise this sort of setup may feel like unnecessary or overly complicated. In case you are new to Git, I highly recommend you go through /tutorial/git and also have a look at /cheatsheet/git.</description><author>Jayson Salazar Rodriguez | @jdsalaro | Blog</author><pubDate>Tue, 14 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jdsalaro.com/tutorial/git-configuration-folder-dependent-conditional-includes/</guid></item><item><title>Restyling with Tailwind CSS</title><link>https://blog.reiterate.app/software/2023/11/13/restyling-with-tailwind-css/</link><description>I’ve revamped the styling on the blog, completely replacing the old (and rather dated) theme with Tailwind CSS. That also meant changing the build process to accomodate Tailwind.</description><author>Reiterations</author><pubDate>Tue, 14 Nov 2023 01:25:00 GMT</pubDate><guid isPermaLink="true">https://blog.reiterate.app/software/2023/11/13/restyling-with-tailwind-css/</guid></item><item><title>Childhood Bullying II – Aftermath</title><link>https://callmeo.live/blog/childhood-bullying-ii-aftermath/</link><description>&lt;p&gt;&lt;a href="../childhood-bullying-i-experiences"&gt;Part one&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;With it being a decade since I was last at school, you&amp;rsquo;d think I&amp;rsquo;d be over it. Sometimes I think I am.&lt;/p&gt;
&lt;p&gt;And then I dream.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m suddenly back at school, or a face I had forgotten comes back to me, and it all comes back: the powerlessness, humiliation, the anger that someone could hurt me so much for kicks – and finally the horror that I&amp;rsquo;m unable to stop them. Everything comes back. Not even waking up removes the fear, as I spend the rest of my day trying to keep my composure together.&lt;/p&gt;
&lt;p&gt;All it takes is the smallest reminder and the pain rushes back as if I was there again, and it shows how much it destroyed me. It tells me who I am, reminds me that I&amp;rsquo;m weak, anxious, and ready to crumble at the slightest struggle.&lt;/p&gt;
&lt;p&gt;Everyone&amp;rsquo;s heard the what doesn&amp;rsquo;t kill you platitude. It&amp;rsquo;s not true. If I was strong I wouldn&amp;rsquo;t be bothered by what happened ages ago, and I wouldn&amp;rsquo;t be left with this lasting fear of existing in the real world.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m a shell of a person. When I was young I found out that the best way to cope with everything was to isolate, and a flickering screen was more inviting than the people out there. That preference became a habit, and to this day I struggle with existing in the real world. Every time I&amp;rsquo;ve taken steps to regain a semblance of normality or confidence, things would swiftly collapse and leave me off in a worse state – and after a short time this became a self-fulfilling prophecy.&lt;/p&gt;
&lt;p&gt;When I step out in the world, it&amp;rsquo;s immediately obvious to others that I&amp;rsquo;m different – wrong – on some level. Childhood bullying scars you, it paints a target on your back that&amp;rsquo;s apparent to others and most noticeable to those who can capitalise on it. There&amp;rsquo;s no escaping it.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;If there&amp;rsquo;s one thing that&amp;rsquo;s stuck with me over the years, it&amp;rsquo;s anger. Anger at those who had no right to harm me, at myself for never fighting back, and at adults who neglected their duty of care – especially those who knew was going on yet did nothing.&lt;/p&gt;
&lt;p&gt;And finally my anger lands at society, that big amorphous blob which shapes everything around us and how it makes us view bullying. How it&amp;rsquo;s shameful to have been bullied. How it&amp;rsquo;s easier to ignore such a problem than deal with it. How we say boys will be boys to cover up, dismiss, and outright excuse pain willingly inflicted on others.&lt;/p&gt;
&lt;div class="centre"&gt;&lt;p&gt;&lt;strong&gt;Have you ever stopped and looked at the word bullying?&lt;/strong&gt;&lt;/p&gt;&lt;/div&gt;

&lt;p&gt;It&amp;rsquo;s a silly word. It&amp;rsquo;s harmless, inane. &lt;strong&gt;It doesn&amp;rsquo;t remotely convey the acts which can constitute it, if anything the word bullying actively ridicules and diminishes the act altogether.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We recognise that assault, threats, stalking, theft, and harassment are extremely harmful acts – we literally have laws against these – yet labelling it as bullying makes it trivial, something minor to be glossed over. &lt;strong&gt;If we said that a child committed premeditated assault against a peer, we&amp;rsquo;d rightly be horrified. Calling it bullying undermines the serious nature of such acts, letting it be swept under the rug.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We trivialise the harms of bullying by calling it that, tacitly telling victims that it&amp;rsquo;s silly to be in pain over it. It&amp;rsquo;s the norm to dismiss and humiliate those who who were bullied, and there&amp;rsquo;s no big push to re-examine this norm.&lt;/p&gt;
&lt;p&gt;The effects of childhood bullying are well known: increased rates of depression, anxiety, and suicide. And &lt;a href="https://www.bbc.co.uk/news/education-23756749"&gt;the effects live long into adult life&lt;/a&gt;. Yet there&amp;rsquo;s no big push to eliminate it or the stigma of being bullied.&lt;/p&gt;
&lt;p&gt;When speaking up leads to ridicule, it&amp;rsquo;s no wonder we choose to suffer in silence.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;For the longest time I&amp;rsquo;ve avoided talking about bullying due to the shame of it. Writing this and putting it up in a public space is daunting, but there&amp;rsquo;s no other way I can get it out there.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m a broken adult, wracked with crushing anxieties about life due to my experiences around others at school. Even if the last years of Secondary got better and College was a blast, it doesn&amp;rsquo;t make up for an entire childhood where I was never safe around others.&lt;/p&gt;
&lt;p&gt;I struggle with existing, with being me, putting myself and my work out there in the wider world. I struggle with being a bloke and fitting around others. I hate having existed and being remembered to the point where I hate to use my real name – the moments where I can forget who I am are bliss. If there was a painless and instant way to give up I would have done it years ago.&lt;/p&gt;
&lt;p&gt;At the start of this year, I decided to do something to absolve me from the pain I carry – I mentally forgave everyone who wronged me. It was nice, and a few months I managed to feel comfortable with being me in the world.&lt;/p&gt;
&lt;p&gt;And then I dream.&lt;/p&gt;
&lt;p&gt;How can I forgive those who hurt me so much?&lt;/p&gt;
&lt;p&gt;&lt;a href="../childhood-bullying-iii-things-left-unsaid"&gt;Part three&lt;/a&gt;&lt;/p&gt;</description><author>callmeolive</author><pubDate>Mon, 13 Nov 2023 23:25:00 GMT</pubDate><guid isPermaLink="true">https://callmeo.live/blog/childhood-bullying-ii-aftermath/</guid></item><item><title>Childhood Bullying I – Experiences</title><link>https://callmeo.live/blog/childhood-bullying-i-experiences/</link><description>&lt;p&gt;This June marked 10 years since I left school.&lt;/p&gt;
&lt;p&gt;I was the weird kid. I don&amp;rsquo;t know if I was innately weird, or if years of bullying turned me that way. Either way growing up wasn&amp;rsquo;t fun.&lt;/p&gt;
&lt;p&gt;When I was 7 years old my family had to move across the country, I landed in a new school at the point where friendships and social dynamics were ironed out – if it wasn&amp;rsquo;t apparent that there was no room for me, my new classmates would make sure I knew.&lt;/p&gt;
&lt;p&gt;I was a sensitive lad, as if being the new kid wasn&amp;rsquo;t enough to lock me out. It wouldn&amp;rsquo;t take much for me to cry and when the rest of the class found that out my life was over. Barring two lads, every boy in that class swarmed me in an instant, and daily life became one of punches and bruises – boys goading one another to see who could make me break down the quickest. I&amp;rsquo;d be lucky if I could go a day without having a small mob actively going out of their way to harass and assault me – I became a valid target of aggression, and nothing was off-limits.&lt;/p&gt;
&lt;p&gt;Swiftly it became part of class culture that I wasn&amp;rsquo;t equal, and the fact I was lesser permeated every aspect of school life. Anything to be shared wasn&amp;rsquo;t passed on to me, my possessions became anyone&amp;rsquo;s possessions, and my existence was only acknowledged when I was to be laughed at. There were no qualms about whether it was right to hurt me – to them it was entertainment like any other.&lt;/p&gt;
&lt;p&gt;I can&amp;rsquo;t describe what being in a hostile environment like that does to a young mind, &amp;lsquo;soul-destroying&amp;rsquo; doesn&amp;rsquo;t remotely cover it. Whatever social skills I had were pummelled out of me to the point where during one day of humiliation, all I could scream through confused tears was &amp;ldquo;Why are you laughing at me?&amp;rdquo; at a crowd of goading onlookers – that plea as a desperate, damaged boy was mockingly echoed back at me for the rest of my life.&lt;/p&gt;
&lt;p&gt;Isolation became my only solace on the playground. The good days consisted of spending my breaks idly pacing with my face pointing down and being left alone – but that would never stop anyone from approaching me to cause harm. There was never a safe moment on the playground, and I don&amp;rsquo;t have the words to convey what that did to me.&lt;/p&gt;
&lt;p&gt;I was only 7 years old. No-one had the right to lay a finger on me.&lt;/p&gt;
&lt;p&gt;I was always the kid who followed rules at school, I believed that they were unbreakable tenets of life which would come around to protect me. That, and a fear for getting into trouble meant that when I was told that I shouldn&amp;rsquo;t get into fights and to tell a teacher if someone was harassing me. As other victims of childhood bullying will tell you, that did nothing solve the problem. On a good day a teacher might get a crowd of lads to pack it in for 10 minutes, but the second backs were turned I&amp;rsquo;d be kicked to the floor again.&lt;/p&gt;
&lt;p&gt;I remember one December day with heavy snow. The staff decided to let us enjoy the magic. A school-wide snowball fight was arranged on the field, and the playground was reserved for those who didn&amp;rsquo;t want to participate; knowing I&amp;rsquo;d be chased around and have snow piled in my face and shoved down my clothes, I stayed in the safe zone surrounded by teachers – Not that it stopped anything. One of the teachers had the nerve to say it couldn&amp;rsquo;t have happened, as if my clothes were bone dry before them.&lt;/p&gt;
&lt;p&gt;Staff neglecting their duty of care was common throughout my school life, and I cannot express my pain over it.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I was 9 now. My parents were increasingly distraught over the fresh bruises I&amp;rsquo;d return home with, and while I can&amp;rsquo;t remember if I told them what happened, I know that a single dinner lady blew the whistle to them. It wasn&amp;rsquo;t long before my mum witnessed me one day being punched to the ground moments after dropping me off at the playground – she immediately stormed back in, raising hell at nearby teachers for ignoring it before dragging me back home in tears.&lt;/p&gt;
&lt;p&gt;The next day my parents had a meeting with the headmistress, wherein she finally admitted that there was a problem with bullying and said she&amp;rsquo;d do whatever she could to prevent it – aside from remove those who purposely and repeatedly assaulted me. My parents weren&amp;rsquo;t buying it, and I was taken out of that school.&lt;/p&gt;
&lt;p&gt;I was moved to another primary school a mile away. It would&amp;rsquo;ve been nice, but I was already a broken, different child. All it took was for a single boy to cotton on to it before it all started again. What didn&amp;rsquo;t help was that the school was still local, many of the kids knew those from my old school and it wasn&amp;rsquo;t long before I was asked were you the boy who&amp;hellip; Followed by a painful memory.&lt;/p&gt;
&lt;p&gt;The bullying here started again. It was smaller in scale, less physical but more social. There was one boy in particular who&amp;rsquo;d assault me during certain games so that when I&amp;rsquo;d go to a teacher about what he was doing, the easiest option would be to ban the game altogether – dodgeball in particular. I remember him using that as an excuse to repeatedly pelt me in the face even if I wasn&amp;rsquo;t part of the game, and he gleeful that he could pin the blame of it being banned on me.&lt;/p&gt;
&lt;p&gt;One clear memory is that the new school&amp;rsquo;s playground was in the shape of a horseshoe around the main building, with a little connecting path off-limits for pupils. One day I was &amp;lsquo;it&amp;rsquo; for hide and seek, up against the rest of my class, bar a single girl. It wasn&amp;rsquo;t long before I spotted them all bundled together – with one of them making eye contact with me – before running off. I gave chase, stopping before the path we were banned to go on. Tracking all the way back around I noticed the same happen, the rest of my class huddling back down the off-limits path, pretending they weren&amp;rsquo;t spotted. I approached a member of staff about them going down the banned path (again, I was a kid who always followed and respected rules), but they shrugged it off and asked how they could stop so many of them going down that class.&lt;/p&gt;
&lt;p&gt;Returning inside and talking to my teacher, he wasn&amp;rsquo;t having it. He went out of his way to confront the rest of the class when break ended. Funny how their laughs suddenly stopped.&lt;/p&gt;
&lt;p&gt;All I can think about was the one girl who was kind enough not to join in.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Secondary school wasn&amp;rsquo;t fun either. Kids from both the primary schools were part of it, and they spread things about me to the rest of the kids around the school. It wasn&amp;rsquo;t quick before they all cottoned on. The first few years were hellish.&lt;/p&gt;
&lt;p&gt;It was at secondary where I found myself blowing up. I was irritable and quick to anger at the smallest of provocations, and I still hate myself for it.&lt;/p&gt;
&lt;p&gt;I find it difficult to talk about what happened. All I&amp;rsquo;m thankful for was that my head of year changed in year 9, from an English teacher who laughed at my parent&amp;rsquo;s concerns to a Geography teacher who took my safety seriously. He stuck his head out to get me help, and arranged for in-school counselling and anger management during the rougher times.&lt;/p&gt;
&lt;p&gt;Secondary school slowly got better as folks matured and I learned how to cope better. When I moved on to college – far away, with completely new people – I found myself surrounded by other self-prescribed weirdos, many of whom faced the same as I did. They were a wonderful bunch, and for the first time in my life I felt comfortable, safe, and that life could actually be wonderful.&lt;/p&gt;
&lt;p&gt;How long did I have to wait for just a moment of peace?&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;a href="../childhood-bullying-ii-aftermath"&gt;Part two&lt;/a&gt;&lt;/p&gt;</description><author>callmeolive</author><pubDate>Mon, 13 Nov 2023 23:20:00 GMT</pubDate><guid isPermaLink="true">https://callmeo.live/blog/childhood-bullying-i-experiences/</guid></item><item><title>Rituals Circa 2023</title><link>http://evantravers.com/articles/2023/11/13/rituals-circa-2023/</link><description>&lt;p&gt;&lt;img alt="" src="https://images.evantravers.com/articles/2023/11/review.jpeg" /&gt;&lt;/p&gt;&lt;p&gt;I have a &lt;strong&gt;lot&lt;/strong&gt; of checklists and systems. Definitely too much. My friend Errin asked if he could steal the templates, so some quick thoughts.&lt;/p&gt;
&lt;p&gt;Rituals help me by…&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Providing a sense of progression and time-pass when working remote.&lt;/li&gt;
&lt;li&gt;Holding long-term goals in front of my daily thoughts.&lt;/li&gt;
&lt;li&gt;Correctly balancing lead and lag measures.&lt;sup class="footnote-ref"&gt;&lt;a href="#fn-lag" id="fnref-lag"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;Highlighting Weak Signals&lt;sup class="footnote-ref"&gt;&lt;a href="#fn-weak" id="fnref-weak"&gt;2&lt;/a&gt;&lt;/sup&gt; before problems escalate with health or relationships.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I am a crazy man. I have built this up over &lt;em&gt;years&lt;/em&gt;. Don't copy this verbatim. Steal one thing that's good, but be inspired but terrified by the rest.&lt;/p&gt;
&lt;h2&gt;&lt;a class="anchor" href="#️-daily-journaldailyyyyy-mm-ddmd" id="️-daily-journaldailyyyyy-mm-ddmd"&gt;&lt;/a&gt;🗒️ Daily: &lt;code&gt;journal/daily/YYYY-MM-DD.md&lt;/code&gt;&lt;/h2&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-markdown" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;---&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;follower&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;husband&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;father&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;health&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;work&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;plan&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;---&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the template for the daily note. I rate myself on my intentions from 1-3 in areas of my life:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1 = Didn't try.&lt;/li&gt;
&lt;li&gt;2 = Showed up.&lt;/li&gt;
&lt;li&gt;3 = Broke a sweat.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The following becomes a sort of &amp;quot;&lt;a href="https://evantravers.com/articles/tags/bullet-journal/"&gt;bullet journal&lt;/a&gt;&amp;quot; for what happened that day. Highlights, funny things my kids said, insights from meetings at work… etc.&lt;/p&gt;
&lt;h3&gt;&lt;a class="anchor" href="#-daily-plan-workdays--9a" id="-daily-plan-workdays--9a"&gt;&lt;/a&gt;🌅 Daily Plan (Workdays @ 9a)&lt;/h3&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-markdown" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;# 🧩 Daily Plan
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;In order to be effective with my day, I design my day with the right balance of focus, time, and effort.
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 🙏 Pray. Wisdom to know, strength to do.
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; ⭐ Protect the [essential](things:///show?id=today&amp;amp;filter=@Meazure%20Learning,$High).
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 👨‍👩‍👧‍👦 Prioritize [family projects](things:///show?id=anytime&amp;amp;filter=@Family).
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 👨 Choose [personal tasks](things:///show?id=anytime&amp;amp;filter=@Me).
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 📅 Check schedule.
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 🕊 Make room for margin.
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;Tags: Rituals: Daily
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This ritual gets done 50% of the time. I mostly plan my day the previous night in the Workday Shutdown ritual. This one is designed to help me remember what's important, fill the remaining plan with useful intention, and stay healthy.&lt;/p&gt;
&lt;h3&gt;&lt;a class="anchor" href="#-workday-shutdown-workdays--4p" id="-workday-shutdown-workdays--4p"&gt;&lt;/a&gt;🌇 Workday Shutdown (Workdays @ 4p)&lt;/h3&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-markdown" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;# 🗃 Workday Shutdown
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;As a faithful steward, I put my work in order to be prepared for the next day, identifying the essential task for the next day.
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; Open the [Daily Note](obsidian://advanced-uri?vault=wiki&amp;amp;commandname=Periodic%20Notes%3A%20Open%20daily%20note).
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 🕰 Review [time logs](https://track.toggl.com/timer) and time blocks. Mark misspent time on Calendar blocks.
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 📓 [Log tasks/projects](things:///show?id=logbook).
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 📝 Process notebook.
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 📨 Process [Drafts inboxes](drafts://).
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 📥 Dump your brain into the Inbox. Get it all out.
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 📤 Process [Things inbox](things:///show?id=inbox). (:estimate, $focus, !modality, priority)
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 🗓 Time block [tomorrow](things:///show?id=tomorrow).
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 😴 Shut down. (Close. Speak. Shut. Trash. Door. Go home.)
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 👨‍👩‍👧‍👦 Go live in Coram Deo this evening.
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;Tags: Rituals: Daily
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This ritual is the rock of my whole system. I go through my inboxes, give all the straggling thoughts a home, log everything I intend to log, and leave the desk for the evening.&lt;/p&gt;
&lt;p&gt;This I originally installed as a response to overworking after reading &lt;a href="https://evantravers.com/articles/2019/02/09/review-deep-work-by-cal-newport/"&gt;Deep Work&lt;/a&gt;, but it's expanded to be &amp;quot;manager time&amp;quot; to keep me on track.&lt;/p&gt;
&lt;h2&gt;&lt;a class="anchor" href="#️-weekly-journalweeklyyyyy-wweek-numbermd" id="️-weekly-journalweeklyyyyy-wweek-numbermd"&gt;&lt;/a&gt;🗒️ Weekly: &lt;code&gt;journal/weekly/YYYY-W[week number].md&lt;/code&gt;&lt;/h2&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-markdown" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;---&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;Goal1&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;Goal2&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;---&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;# Weekly Review: date%3Agggg-%5BW%5Dww
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;[!tldr] ![[journal/yearly/2023#Theme]]
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;## Planning
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;## Review
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;### 👍:
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;### 👎:
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;### ✅:
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;---
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;​```dataviewjs
&lt;/div&gt;&lt;div class="line"&gt;let p = function(b) &amp;amp;lbrace
&lt;/div&gt;&lt;div class="line"&gt;  switch(b) &amp;amp;lbrace
&lt;/div&gt;&lt;div class="line"&gt;    case &amp;quot;&amp;quot;: return &amp;quot;&amp;quot;; break;
&lt;/div&gt;&lt;div class="line"&gt;    case &amp;quot; &amp;quot;: return &amp;quot;&amp;quot;; break;
&lt;/div&gt;&lt;div class="line"&gt;    case 0: return &amp;quot;⚫&amp;quot;; break;
&lt;/div&gt;&lt;div class="line"&gt;    case 1: return &amp;quot;🔴&amp;quot;; break;
&lt;/div&gt;&lt;div class="line"&gt;    case 2: return &amp;quot;🟡&amp;quot;; break;
&lt;/div&gt;&lt;div class="line"&gt;    case 3: return &amp;quot;🟢&amp;quot;; break;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;  &amp;amp;rbrace
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;&amp;amp;rbrace
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;let f = function(d) &amp;amp;lbrace
&lt;/div&gt;&lt;div class="line"&gt;  return `[[$&amp;amp;lbraced.obsidianLink()&amp;amp;rbrace|$&amp;amp;lbracemoment(d, 'YYYY-MM-DD').format(&amp;quot;dd&amp;quot;)&amp;amp;rbrace]]`;
&lt;/div&gt;&lt;div class="line"&gt;&amp;amp;rbrace
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;let start = moment(dv.current().file.name, &amp;quot;gggg-[W]ww&amp;quot;);
&lt;/div&gt;&lt;div class="line"&gt;let end = start.clone().add(6, 'd');
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;dv.header(2, `Dailies: $&amp;amp;lbracestart.format(&amp;quot;MM-DD&amp;quot;)&amp;amp;rbrace - $&amp;amp;lbraceend.format(&amp;quot;MM-DD&amp;quot;)&amp;amp;rbrace`)
&lt;/div&gt;&lt;div class="line"&gt;dv.table(
&lt;/div&gt;&lt;div class="line"&gt;  [&amp;quot;📆&amp;quot;, &amp;quot;🙏&amp;quot;, &amp;quot;👫&amp;quot;, &amp;quot;👨‍👩‍👧‍👦&amp;quot;, &amp;quot;💪&amp;quot;, &amp;quot;🖌&amp;quot;, &amp;quot;💻&amp;quot;, &amp;quot;🎯&amp;quot;], dv.pages('&amp;quot;journal/daily&amp;quot;')
&lt;/div&gt;&lt;div class="line"&gt;    .filter(d =&amp;gt; moment(d.file.name).isBetween(start, end, undefined, '[]'))
&lt;/div&gt;&lt;div class="line"&gt;    .sort(d =&amp;gt; d.file.day, 'asc')
&lt;/div&gt;&lt;div class="line"&gt;    .map(d =&amp;gt; [f(d.file.link), p(d.follower), p(d.husband), p(d.father), p(d.health), p(d.create), p(d.work), p(d.plan)])
&lt;/div&gt;&lt;div class="line"&gt;)
&lt;/div&gt;&lt;div class="line"&gt;​```
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the weekly template where all the weekly events show up. It rolls up the &lt;a href="https://evantravers.com/articles/2022/09/12/journaling-and-reviews-in-obsidian/#weekly"&gt;daily ratings into something digestible&lt;/a&gt;… it's pretty common for me to spot little patterns that I need to address.&lt;/p&gt;
&lt;p&gt;The Goals at the top are found in the Quarterly note and roll up there eventually.&lt;/p&gt;
&lt;h3&gt;&lt;a class="anchor" href="#️-weekly-review-fridays--4p" id="️-weekly-review-fridays--4p"&gt;&lt;/a&gt;🗃️ Weekly Review (Fridays @ 4p)&lt;/h3&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-markdown" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;[!info] Live _coram deo_. Plan this week in the presence of God.
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;## 📓 Prep
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 🙏: Pray. Ask the Lord for wisdom and clarity on what is important eternally.
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 📓: Review journal and [daily notes](obsidian://advanced-uri?vault=wiki&amp;amp;commandname=Periodic%20Notes%3A%20Open%20weekly%20note).
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; ✅: What did you [accomplish](things:///show?id=logbook)?
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 💭: What can you learn from last week?
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 📈: Put logged successes in [OKR system in ADP](https://workforcenow.adp.com/theme/index.html#/Myself/MyselfTabTalentCategoryPerformanceGoals).
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 📝 Get Clear:
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; Do a brain dump. Add any tasks or projects you come up with to the Things inbox.
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; Process your physical inbox. Create tasks in Things for each item in your physical inbox that you want to take action on.
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; Process your email inbox. Use Mail to Things to forward emails you need or want to take action on to your Things inbox.
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; Process your Things inbox. Assign each task to an area or to a project. Tag appropriately. (:estimate, $focus, !categorize, priority)
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; Go through each of your projects. Use the checklist[^checklist].
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;[^checklist]:
&lt;/div&gt;&lt;div class="line"&gt;    ## 📂 For Each Area:
&lt;/div&gt;&lt;div class="line"&gt;    - &lt;span&gt;[&lt;/span&gt; &lt;span&gt;]&lt;/span&gt; Are your projects in correct priority order?
&lt;/div&gt;&lt;div class="line"&gt;    
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;    ### 📁 For Each Project:
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;    &lt;span&gt;- [ ] Is this project still relevant?
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;    &lt;span&gt;- [ ] Can I delegate this project?
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;    &lt;span&gt;- [ ] Should I move this project to Someday?
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;    &lt;span&gt;- [ ] Are there are tasks I have already completed?
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;    &lt;span&gt;- [ ] Are there any tasks I want to delete?
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;    &lt;span&gt;- [ ] Am I happy with the structure of the project? E.g., should I add or change headings?
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;    &lt;span&gt;- [ ] Do all tasks with deadlines have the correct deadline set?
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;    &lt;span&gt;- [ ] Could I add useful notes to any tasks or to the project itself?
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;    &lt;span&gt;- [ ] Are any new tasks for this project not yet in Things?
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;    &lt;span&gt;- [ ] Should I convert any tasks to separate projects?
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;    &lt;span&gt;- [ ] Is there a clear 'next action' for this project? (If not, break down your projects or tasks into smaller tasks until there is a clear next action.)
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;    &lt;span&gt;- [ ] How can you serve and surprise in this area?
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;&lt;span&gt;## 📅 Plan
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; Look ahead at the week. Using Deadlines and Upcoming, examine the week and month to find tasks that need to be done, and space them out through the week.
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; Make time for important work. Create 4 hour strategic blocks and identify some buffer blocks.
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; Schedule in the most important task for the day. Using [Upcoming](things:///show?id=upcoming&amp;amp;filter=%40Meazure%20Learning%2CEstimates), schedule the important task for the day.
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Besides the Workday Shutdown, this is the other ritual that keeps everything running.&lt;/p&gt;
&lt;p&gt;Parts of the list came from Peter Akkies, some of it is &lt;a href="https://evantravers.com/articles/2023/01/27/weekly-reviews-in-things-app-using-shortcuts/"&gt;automated&lt;/a&gt; now.&lt;/p&gt;
&lt;h3&gt;&lt;a class="anchor" href="#-family-weekly-fridays--7p" id="-family-weekly-fridays--7p"&gt;&lt;/a&gt;🫂 Family Weekly (Fridays @ 7p)&lt;/h3&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-markdown" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;# 📖 Weekly Meeting
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;## Rapport
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 🙏🏻 Prayer.
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 👂 How are you in one word?
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; Screen Time Check
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;## What's the plan?
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 🗓 Check [last week’s entry](obsidian://advanced-uri?vault=wiki&amp;amp;commandid=periodic-notes%253Aprev-weekly-note).
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 📉 Check the [Monthly Plan](obsidian://advanced-uri?vault=wiki&amp;amp;commandid=periodic-notes%253Aopen-monthly-note).
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 📈 Check our progress against [12WY Scoreboard](obsidian://advanced-uri?vault=wiki&amp;amp;commandid=periodic-notes%253Aopen-quarterly-note).
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;## What should we change?
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 🚦 Start / Stop / Continue:
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 🟢 What should we start?
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 🟡 What should we continue?
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 🔴 What should we stop?
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 🪨 Big Rocks…
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; Spiritual Leadership?
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; School?
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; Next House?
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; Good Vibes Only?
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; Leaving and Cleaving?
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;## Follow through.
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; ❓ Any [questions](things:///show?id=anytime&amp;amp;filter=@Family) to answer?
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 📆 [What are we committed to next week](fantastical2://show?date=week)?
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 🧱 Set aside time blocks:
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; Groceries and errands?
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; Family-together time?
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; Fellowship with other families?
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; Time to work/talk together on the Big Problems?
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; Projects? (Walking project dictation to unpack and delegate)
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 💴 Budget Update.
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;## Reset our purpose.
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; ⛰ What are you going to look forward to this week?
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 🤗 Express gratitude.
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;Tags: @Family, Rituals: Weekly
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This ritual changes a good bit as my wife and I find new things to work on together. One recent adjustment is to have a &lt;code&gt;🪨 Big Rocks&lt;/code&gt; header that contains nudges to check in on important conversations or projects.&lt;/p&gt;
&lt;h2&gt;&lt;a class="anchor" href="#️-monthly-journalmonthlyyyyy-mmmd" id="️-monthly-journalmonthlyyyyy-mmmd"&gt;&lt;/a&gt;🗒️ Monthly: &lt;code&gt;journal/monthly/YYYY-MM.md&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;(Monthly doesn't have a format at the moment.)&lt;/p&gt;
&lt;h3&gt;&lt;a class="anchor" href="#️-family-monthly-last-friday-of-month" id="️-family-monthly-last-friday-of-month"&gt;&lt;/a&gt;🗓️ Family Monthly (Last Friday of Month)&lt;/h3&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-markdown" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;# 📆 Family Monthly Meeting
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;The goal is to be through this in less than thirty minutes,[^format] in addition to the [[Family Weekly]].
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 👈 Review the [last month's plan](obsidian://advanced-uri?vault=wiki&amp;amp;commandid=periodic-notes%253Aprev-monthly-note) and [quarterly plan](obsidian://advanced-uri?vault=wiki&amp;amp;commandid=periodic-notes%253Aopen-quarterly-note).
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 📆 Look ahead at schedule for the month…
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 🔨 Identify this month's most important event or project?
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 🏆 Define success for the month. In this month…
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; What is a task or routine that will help us work towards best possible week sometime this month?
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 📈 What micro-adjustment do we want to make to our daily schedule?
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; ⛰ What Real Life Experience are we going to have this month?
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 🧘 One way we can protect margin this month is…
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 🏠 Prioritize home maintenance backlog.
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 🗑️ Pick a declutter space and budget time.
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; Pick a &amp;quot;Deep Cleaning&amp;quot; day.
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 💸 Create/adjust budget goals.
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; 🛑 Block off days to succeed at our projects…
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;Tags: @Sarah Travers, Rituals: Monthly
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;[^format]: Taken in part from Plan Your Year 2020.
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Family Monthly is a touchstone for making sure we understand how crammed our current commitments are and make space for weekend projects like painting or tackling a closet.&lt;/p&gt;
&lt;h2&gt;&lt;a class="anchor" href="#️-quarterly-journalquarterlyyyyy-qnmd" id="️-quarterly-journalquarterlyyyyy-qnmd"&gt;&lt;/a&gt;🗒️ Quarterly: &lt;code&gt;journal/quarterly/YYYY-Q[N].md&lt;/code&gt;&lt;/h2&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-markdown" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;---&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;# rate your outcomes from 1 to 5 in the following roles…&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;roles&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;-&lt;/span&gt; &lt;span&gt;Follower&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;-&lt;/span&gt; &lt;span&gt;Husband&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;-&lt;/span&gt; &lt;span&gt;Father&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;-&lt;/span&gt; &lt;span&gt;Friends&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;-&lt;/span&gt; &lt;span&gt;Work&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;-&lt;/span&gt; &lt;span&gt;Hobbies&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;-&lt;/span&gt; &lt;span&gt;Emotions&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;-&lt;/span&gt; &lt;span&gt;Mental&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;-&lt;/span&gt; &lt;span&gt;Health&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;-&lt;/span&gt; &lt;span&gt;Rest&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;-&lt;/span&gt; &lt;span&gt;Finances&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;---&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;## Plan
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;### Goals
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;#### Goal 1: _Goal_
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;#### Goal 2: _Goal_
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;## Review
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;### Where are you in each area of life?
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;​```dataviewjs
&lt;/div&gt;&lt;div class="line"&gt;// we have to sort somehow, so we convert the names to numbers and sort
&lt;/div&gt;&lt;div class="line"&gt;const s = function(i) &amp;amp;lbrace
&lt;/div&gt;&lt;div class="line"&gt;  return Number(i.replace('-Q', ''));
&lt;/div&gt;&lt;div class="line"&gt;&amp;amp;rbrace
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;const allQuarters =
&lt;/div&gt;&lt;div class="line"&gt;  dv.pages('&amp;quot;journal/quarterly&amp;quot;')
&lt;/div&gt;&lt;div class="line"&gt;    .array()
&lt;/div&gt;&lt;div class="line"&gt;    .sort(function(a, b) &amp;amp;lbrace
&lt;/div&gt;&lt;div class="line"&gt;      return s(a.file.name) - s(b.file.name);
&lt;/div&gt;&lt;div class="line"&gt;    &amp;amp;rbrace)
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;let currentIndex = allQuarters.findIndex((q) =&amp;gt; q.file.name == dv.current().file.name);
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;const quarters = allQuarters.slice(Math.max(currentIndex - 3, 0), currentIndex+1);
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;let datasets = [];
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;let trans = function(color) &amp;amp;lbrace
&lt;/div&gt;&lt;div class="line"&gt;  return color.replace(', 1)', ', 0.5)');
&lt;/div&gt;&lt;div class="line"&gt;&amp;amp;rbrace
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;let colors&lt;span&gt; = [
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;  'rgba(105,105,105, 1)',
&lt;/div&gt;&lt;div class="line"&gt;  'rgba(169,169,169, 1)',
&lt;/div&gt;&lt;div class="line"&gt;  'rgba(211,211,211, 1)',
&lt;/div&gt;&lt;div class="line"&gt;]
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;const purple = 'rgba(139,108,239, 1)';
&lt;/div&gt;&lt;div class="line"&gt;let highlight = '#fff';
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;if (document.body.classList.contains('theme-light')) &amp;amp;lbrace
&lt;/div&gt;&lt;div class="line"&gt;  colors.reverse();
&lt;/div&gt;&lt;div class="line"&gt;  highlight = '#000';
&lt;/div&gt;&lt;div class="line"&gt;&amp;amp;rbrace
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;colors.push(purple);
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;for (let i in quarters) &amp;amp;lbrace
&lt;/div&gt;&lt;div class="line"&gt;  let quarter = quarters[i];
&lt;/div&gt;&lt;div class="line"&gt;  if (quarter.hasOwnProperty('roles')) &amp;amp;lbrace
&lt;/div&gt;&lt;div class="line"&gt;    let values = quarter.roles.map(o =&amp;gt; Object.values(o)).flat()
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;    datasets.unshift(&amp;amp;lbrace
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;    &lt;span&gt;  label: quarter.file.name,
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;    &lt;span&gt;  data: values,
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;    &lt;span&gt;  fill: true,
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;    &lt;span&gt;  backgroundColor: trans(colors[i]),
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;    &lt;span&gt;  borderColor: colors[i],
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;    &lt;span&gt;  pointBackgroundColor: colors[i],
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;    &lt;span&gt;  pointBorderColor: highlight,
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;    &lt;span&gt;  pointHoverBackgroundColor: highlight,
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;    &lt;span&gt;  pointHoverBorderColor: colors[i]
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;    &lt;span&gt;&amp;amp;rbrace)
&lt;/div&gt;&lt;div class="line"&gt;  &lt;/span&gt;&amp;amp;rbrace
&lt;/div&gt;&lt;div class="line"&gt;&amp;amp;rbrace
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;let data   = dv.current().roles
&lt;/div&gt;&lt;div class="line"&gt;let keys   = data.map(o =&amp;gt; Object.keys(o)).flat()
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;const chartData&lt;span&gt; = &amp;amp;lbrace
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;  type: 'radar',
&lt;/div&gt;&lt;div class="line"&gt;  data: &amp;amp;lbrace
&lt;/div&gt;&lt;div class="line"&gt;    labels: keys,
&lt;/div&gt;&lt;div class="line"&gt;    datasets: datasets
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;  &amp;amp;rbrace,
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;  options: &amp;amp;lbrace
&lt;/div&gt;&lt;div class="line"&gt;    scales: &amp;amp;lbrace
&lt;/div&gt;&lt;div class="line"&gt;      r: &amp;amp;lbrace
&lt;/div&gt;&lt;div class="line"&gt;        suggestedMin: 0,
&lt;/div&gt;&lt;div class="line"&gt;        suggestedMax: 5
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;      &amp;amp;rbrace
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;&lt;span&gt;    &amp;amp;rbrace
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;&lt;span&gt;  &amp;amp;rbrace
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;&amp;amp;rbrace
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;window.renderChart(chartData, this.container);
&lt;/div&gt;&lt;div class="line"&gt;​```
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;### Scoreboard
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;​```dataviewjs
&lt;/div&gt;&lt;div class="line"&gt;let check = function(bool) &amp;amp;lbrace
&lt;/div&gt;&lt;div class="line"&gt;  if (bool) &amp;amp;lbrace return &amp;quot;✅&amp;quot; &amp;amp;rbrace;
&lt;/div&gt;&lt;div class="line"&gt;  return &amp;quot;⛔&amp;quot;;
&lt;/div&gt;&lt;div class="line"&gt;&amp;amp;rbrace
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;let quarter = parseInt(dv.current().file.name.match(/Q\d/)[0].replace('Q', ''));
&lt;/div&gt;&lt;div class="line"&gt;let year = dv.current().file.name.match(/\d&amp;amp;lbrace4&amp;amp;rbrace/)[0];
&lt;/div&gt;&lt;div class="line"&gt;let start = (quarter-1)*13+1
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;dv.table(
&lt;/div&gt;&lt;div class="line"&gt;  [&amp;quot;&amp;quot;, &amp;quot;Week&amp;quot;, &amp;quot;Goal 1&amp;quot;, &amp;quot;Goal 2&amp;quot;], dv.pages('&amp;quot;journal/weekly&amp;quot;')
&lt;/div&gt;&lt;div class="line"&gt;    .filter(function(f) &amp;amp;lbrace
&lt;/div&gt;&lt;div class="line"&gt;      let num = f.file.name.match(new RegExp(`$&amp;amp;lbraceyear&amp;amp;rbrace-W(\\d+)`));
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;      if (!num) &amp;amp;lbrace return false; &amp;amp;rbrace
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;    &lt;span&gt;  num = parseInt(num[1]);
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;    &lt;span&gt;  return num &amp;gt;= start &amp;amp;&amp;amp; num &amp;lt; start + 12;
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;    &lt;span&gt;&amp;amp;rbrace)
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;    &lt;span&gt;.sort(w =&amp;gt; w.file.name, 'asc')
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;    &lt;span&gt;.map(w =&amp;gt; [(parseInt(w.file.name.match(new RegExp(`$&amp;amp;lbraceyear&amp;amp;rbrace-W(\\d+)`))[1]) - start)+1, w.file.link, check(w[&amp;quot;Goal1&amp;quot;]), check(w[&amp;quot;Goal2&amp;quot;])])
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;)
&lt;/div&gt;&lt;div class="line"&gt;​```
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I've previously &lt;a href="https://evantravers.com/articles/2023/03/28/obsidian-12wy-review-template/"&gt;written about this template&lt;/a&gt;… it's pretty complicated. It rolls up the weekly reports on my 12WY goals and provides an opportunity to rate myself on my roles… and see whether my goals effected any change in those roles.&lt;/p&gt;
&lt;h3&gt;&lt;a class="anchor" href="#-12wy-every-12-weeks" id="-12wy-every-12-weeks"&gt;&lt;/a&gt;📈 12WY (Every 12 weeks)&lt;/h3&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-markdown" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;# 🎯 12WY Review
&lt;/div&gt;&lt;div class="line"&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; Open [12WY Note](obsidian://advanced-uri?vault=wiki&amp;amp;commandid=periodic-notes%253Aopen-quarterly-note).
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; Go through week reviews in Obsidian.
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; What can you learn from 12WY?
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; Rate your intentions on the Wheel of Life.
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; Talk to Sarah.
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; What are you going to do next 12WY differently?
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;[ ]&lt;/span&gt; What are your new goals for the new 12WY?
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;Tags: !Planning, :4h, Rituals: 12WY
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the accompanying check list that goes with the Quarterly template. It's a work in progress. I could probably stand to re-read &lt;a href="https://evantravers.com/articles/2021/05/29/review-12-week-year/"&gt;12 Week Year&lt;/a&gt; and adjust.&lt;/p&gt;
&lt;h2&gt;&lt;a class="anchor" href="#️-yearly-journalyearlyyyyymd" id="️-yearly-journalyearlyyyyymd"&gt;&lt;/a&gt;🗒️ Yearly &lt;code&gt;journal/yearly/YYYY.md&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;No template at the moment… it keeps changing.&lt;/p&gt;
&lt;p&gt;The past few years I've picked &lt;a href="https://www.themesystem.com/"&gt;a yearly theme&lt;/a&gt; and written it in a &lt;code&gt;## Theme&lt;/code&gt; header near the top of the document. This gets referenced in the Weekly template above so that I see it all year long.&lt;/p&gt;
&lt;h3&gt;&lt;a class="anchor" href="#-yearly-review-year-end" id="-yearly-review-year-end"&gt;&lt;/a&gt;🥂 Yearly Review (Year end)&lt;/h3&gt;
&lt;p&gt;I traditionally journal, do &lt;a href="https://evantravers.com/articles/2020/12/28/plan-your-year/"&gt;Plan Your Year&lt;/a&gt;… just think and journal. It's come naturally to me.&lt;/p&gt;
&lt;p&gt;It's funny because writing/journaling about my year is probably my first and oldest ritual. A lot of the ones above exist because I realized that the clarity of review and thinking was helping me get out of trouble… and once a year is too infrequent to stay out of trouble.&lt;/p&gt;
&lt;p&gt;I try to focus on gratitude. I also try to count things that are important to me:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;good dinners with my wife.&lt;/li&gt;
&lt;li&gt;vacations&lt;/li&gt;
&lt;li&gt;weekend adventures with the kids.&lt;/li&gt;
&lt;li&gt;meaningful conversations.&lt;/li&gt;
&lt;li&gt;prayers answered.&lt;/li&gt;
&lt;li&gt;books read.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a class="anchor" href="#" id=""&gt;&lt;/a&gt;…&lt;/h2&gt;
&lt;p&gt;I repeat… don't copy my setup. Find one useful idea and run away. 🏃&lt;/p&gt;
&lt;section class="footnotes"&gt;
&lt;ol&gt;
&lt;li id="fn-lag"&gt;
&lt;p&gt;There is a weird thing about measuring: you should weigh yourself daily, but pay attention to your weekly or monthly average. Most of us pay too much attention to the progress equivalent daily weight and give it more mindshare than we ought. &lt;a class="footnote-backref" href="#fnref-lag"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-weak"&gt;
&lt;p&gt;First encountered in &lt;a href="https://danluu.com/wat/"&gt;Dan Luu's Normalization of Deviance&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Pay attention to weak signals” sure sounds like good advice, but how do we do it? Strong signals are few and far between, making them easy to pay attention to. Weak signals are abundant. How do we filter out the ones that aren't important? And how do we get an entire team or org to actually do it? These kinds of questions can't be answered in a generic way; this takes real thought. We mostly put this thought elsewhere.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Weak Signals carry the same information as a Strong Signal but they usually manifest much earlier as little ripples… long before the tidal wave.&lt;/p&gt;
&lt;p&gt;Weak Signals are very hard to recognize and learn from. &lt;a class="footnote-backref" href="#fnref-weak"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</description><author>trv.rs</author><pubDate>Mon, 13 Nov 2023 19:59:00 GMT</pubDate><guid isPermaLink="true">http://evantravers.com/articles/2023/11/13/rituals-circa-2023/</guid></item><item><title>The cow and the chicken: overcoming resistance to change</title><link>https://abdulapopoola.com/2023/11/13/the-cow-and-the-chicken-overcoming-resistance-to-change/</link><description>The Cow and Chicken are journeying through the countryside and see a diner with a sign that reads "Steak and Eggs". The chicken nudges the cow and cackles delightfully: "Look! Look, Cow!! We're famous!!!". Whereupon, the cow looks at the same sign and snorts derisively: you're involved! I am committed!!</description><author>CodeKraft</author><pubDate>Mon, 13 Nov 2023 17:00:00 GMT</pubDate><guid isPermaLink="true">https://abdulapopoola.com/2023/11/13/the-cow-and-the-chicken-overcoming-resistance-to-change/</guid></item><item><title>MBA Washing</title><link>https://smcleod.net/2023/11/mba-washing/</link><description>"MBA Washing" refers to the phenomenon where individuals or companies, often with a strong theoretical or academic background but limited recent practical experience, adopt and reinterpret industry-specific terminology and cultural movements.</description><author>smcleod.net</author><pubDate>Mon, 13 Nov 2023 11:00:00 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2023/11/mba-washing/</guid></item><item><title>@Transaction Around Test</title><link>https://backendhance.com/blog/2023/transaction-around-test/</link><description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;p&gt;Mir kribbelte es in den Fingern. Direkt zu Beginn von &lt;a href="https://thorben-janssen.com/" rel="noopener noreferrer" target="_blank"&gt;Thorben Janssens&lt;i class="bx bx-link-external"&gt;&lt;/i&gt;&lt;/a&gt;
Session &amp;ldquo;Das große Java Persistence Q&amp;amp;A&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Die erste Frage war (sinngemäß):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Im vorherigen Talk (etwas mit JPA) wurde gesagt man solle an jeden Test &lt;code&gt;@Transactional&lt;/code&gt; schreiben. Dadurch werden die Daten nach dem Test automatisch zurückgerollt. Wir haben das diskutiert und sind uns uneinig. Was ist deine Empfehlung?&amp;rdquo;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Thorbens Antwort machte mich nervös. Er antwortete (auch sinngemäß) mit einer Gegenfrage:&lt;/p&gt;</description><author>Backendhance</author><pubDate>Mon, 13 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/transaction-around-test/</guid></item><item><title>Back to Edinburgh for a mini zookeeper experience</title><link>https://www.planetjones.net/blog/13-11-2023/zookeeper-experience-during-another-trip-to-edinburgh.html</link><description>My second trip report to Edinburgh - this time it was for the mini zookeeper experience at Edinburgh Zoo, a visit to Edinburgh Castle and the adjacent Camera Obscura. Edinburgh really loses none of its appeal over time and is a must visit.</description><author>Jonathan Jones homepage: planetjones.net</author><pubDate>Mon, 13 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.planetjones.net/blog/13-11-2023/zookeeper-experience-during-another-trip-to-edinburgh.html</guid></item><item><title>Patterns for Personal Web Sites</title><link>https://vit.baisa.cz/notes/blog/patterns-for-personal-web-sites/</link><description>a list of good practices for personal web sites</description><author>Vít Baisa</author><pubDate>Mon, 13 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://vit.baisa.cz/notes/blog/patterns-for-personal-web-sites/</guid></item><item><title>My first beg bounty</title><link>https://tsak.dev/posts/spf/</link><description>&lt;p&gt;This morning, still a bit sleepy, morning coffee in hand, I came across a Hacker News post about
&lt;a href="https://www.troyhunt.com/beg-bounties/"&gt;Beg Bounties&lt;/a&gt;. Intrigued by the title, having heard about &lt;em&gt;Bug&lt;/em&gt; Bounties
before, I learned about its next logical conclusion and the people that &amp;ldquo;randomly&amp;rdquo; discover security vulnerabilities
and then expect to be paid for their &amp;ldquo;ethical disclosure&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Putting this aside as a &lt;em&gt;Today I Learned&lt;/em&gt; kind of thing, I logged into my work email, only to be greeted by one of
such &amp;ldquo;disclosures&amp;rdquo;, aimed at my employer and subsequently becoming my problem. Formatting exactly as sent, sender and
my employer&amp;rsquo;s &lt;code&gt;$domain&lt;/code&gt; redacted.&lt;/p&gt;</description><author>Look mum, I have a blog on tsak.dev</author><pubDate>Mon, 13 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://tsak.dev/posts/spf/</guid></item><item><title>Introducing Yet Another Rust Resource (or YARR!)</title><link>https://ntietz.com/blog/introducing-yet-another-rust-resource-or-yarr/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;Rust is a hard language to learn, in the scheme&lt;sup class="footnote-reference"&gt;
  &lt;a href="https://ntietz.com/blog/introducing-yet-another-rust-resource-or-yarr/#fn-1" id="fn-ref-1"&gt;1&lt;/a&gt;
&lt;/sup&gt;
 of things.
I've previously talked about &lt;a href="https://ntietz.com/blog/rust-resources-learning-curve/"&gt;why the learning curve is hard&lt;/a&gt; and what we could do about it.
Today, I'm proud to say that there's another resource to help people learn Rust in a more approachable way.&lt;/p&gt;
&lt;p&gt;Introducing &lt;a href="https://yarr.fyi/"&gt;Yet Another Rust Resource&lt;/a&gt;&lt;sup class="footnote-reference"&gt;
  &lt;a href="https://ntietz.com/blog/introducing-yet-another-rust-resource-or-yarr/#fn-2" id="fn-ref-2"&gt;2&lt;/a&gt;
&lt;/sup&gt;
, or YARR.
(Yes, many examples are pirate themed.)
YARR is a short introductory course on Rust which is designed to be completed in just a few days.
The goal is to get you some foundational knowledge and the lay of the land so you can go forth and deepen your knowledge through real-world programming and other books/courses.
When you complete YARR, you should be able to write simple Rust programs and you should have enough familiarity to pair with someone on a bigger Rust program.&lt;/p&gt;
&lt;p&gt;I've linked to YARR previously from my &lt;a href="https://ntietz.com/projects/"&gt;projects page&lt;/a&gt; and soft-launched it with some friends, but never officially announced it.
Whoops!
So here it is, announced and ready to use.&lt;/p&gt;
&lt;p&gt;What follows are some usage suggestions, how to contribute feedback and help, and why this exists in the first place.&lt;/p&gt;
&lt;h1 id="how-to-use-yarr"&gt;How to use YARR!&lt;/h1&gt;
&lt;p&gt;YARR is written to get people up to speed quickly in an environment where they will be able to continue working with an experienced Rust programmer.
This may be a work environment, it may be &lt;a href="https://www.recurse.com/"&gt;the best community for programmers&lt;/a&gt;, or it could be a hobby project where you can pair a lot.
People can also learn entirely independently and use deeper resources after or alongside YARR.&lt;/p&gt;
&lt;p&gt;Here's my suggestion on how to complete the course:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Read/skim through it once without doing the exercises.&lt;/strong&gt;
You won't retain a lot of the material or understand it well on the first read.
The purpose of the first pass is to start to load terms into your head and start building familiarity.
You'll also get a little lay of the land.
If you don't understand something, &lt;em&gt;skip it and move on.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Find another Rust programmer to help you when you have questions or are stuck.&lt;/strong&gt;
The goal with getting comfortable with Rust quickly is to &lt;em&gt;avoid some of the hard parts&lt;/em&gt;, and an experienced Rust programmer will be able to get you unstuck and move past some of the tricky things.
You may want to reach out to people at work, in your communities (&lt;a href="https://www.recurse.com/"&gt;RC&lt;/a&gt; is great for this), friends, or internet strangers.
If you don't have someone to pair with, feel free to email me; I can pair with a few people who are working through this and I can also pair volunteers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Read it carefully and do the exercises (get your friend).&lt;/strong&gt;
Your second pass through it should be a more careful reading.
Do the exercises as you go, and try to see if you generally get the concepts.
This is a great time to work with your Rust partner on this.
As a more experienced Rust programmer they should be able to help you through the tricky bits and help with some of the concepts.
And don't be too hard on yourself, though: You will probably &lt;em&gt;not&lt;/em&gt; understand lifetimes and a lot of the other concepts your first time through.
Those sure took &lt;em&gt;me&lt;/em&gt; a long time to grok, too.
Just see what you understand, see if you can do the exercises, and ask your partner for help!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bookmark it as a resource.&lt;/strong&gt;
Once you've finished the course but before you move on to something else, make sure you save it!
(You can actually do this whenever you like.)
It's a handy resource to come back to for a quick refresher on things, especially after you've gone through something deeper.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Explore more deeply!&lt;/strong&gt;
Now that you've finished YARR, you can move on to &lt;a href="https://yarr.fyi/other-resources"&gt;other resources&lt;/a&gt; and other learning paths.
There are a lot of great books out there on Rust, each with a different flavor, so it is worth looking at multiple for which suits you best.
And you can also dive in more deeply through pair programming: this is how I helped a coworker get more comfortable with Rust (he also did YARR and has read some of a Rust book).
And as you go through these paths, revisit YARR occasionally to get an overview again.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id="feedback-and-help-wanted"&gt;Feedback and help wanted&lt;/h1&gt;
&lt;p&gt;This is just the first version of YARR, which is very much a living document.
I wrote this version by myself with very gracious &lt;a href="https://yarr.fyi/credits#acknowledgements"&gt;feedback&lt;/a&gt; from friends and coworkers, but it needs more to be even better.&lt;/p&gt;
&lt;p&gt;Here's what &lt;em&gt;you&lt;/em&gt; can do to help YARR be even better:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="mailto:me@ntietz.com?subject=Feedback on YARR"&gt;Send me feedback!&lt;/a&gt;&lt;/strong&gt; What worked well for you? What are you still confused on? Is something wrong? Do you have a better pirate example I should include?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="mailto:me@ntietz.com?subject=I'd like to pair on YARR"&gt;Volunteer to pair with learners!&lt;/a&gt;&lt;/strong&gt; It can be hard to find an experienced Rust programmer to pair with so I'm going to see if I can do those pairings. If you want to be paired with someone learning or you want to be paired with a mentor, email me!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Share this post and YARR!&lt;/strong&gt; If you found this material helpful or you think someone else would, &lt;em&gt;please&lt;/em&gt; share it with friends, on your blog, wherever you think someone who can use it will find it. Resources are only as helpful as they are discoverable. I think sharing &lt;em&gt;this post&lt;/em&gt; would be the best introduction into YARR, but do as you feel best.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Send a patch!&lt;/strong&gt; If you want to directly contribute improvements to the content, you can also submit patches to it. Instructions are in &lt;a href="https://sr.ht/~ntietz/yet-another-rust-resource/"&gt;the repo&lt;/a&gt; and you can also email me if you want any pointers on contributing, since email contribution workflows are uncommon.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="why-yarr-exists"&gt;Why YARR exists&lt;/h1&gt;
&lt;p&gt;I wrote the content for the first version of YARR because I wanted—no, &lt;em&gt;needed&lt;/em&gt;—it to exist.
There were a dearth of training materials for quickly getting people up to speed in Rust, and that's a big gap.
We were considering introducing Rust at work, and one of the big challenges with Rust has always been the time to onboard new programmers into it.
When we used Go, we could get someone up to speed in under a week (although mastery takes far longer), so this was a big drawback for Rust.
If we could make this faster, it would aid adoption of Rust at work.&lt;/p&gt;
&lt;p&gt;When Google released the content for &lt;a href="https://google.github.io/comprehensive-rust/"&gt;Comprehensive Rust&lt;/a&gt;, I saw an opportunity to make a similar training course that's run asynchronously.
I can't run a 3-day workshop at work, but I can write the material ahead of time and help people when they get stuck!
So I wrote this material to help my coworkers quickly get up to speed on Rust so that we could use Rust in production.&lt;/p&gt;
&lt;p&gt;The full version here is public and shared because the material was written once, but can be shared many times.
There's no sense in writing good training materials and then keeping them closed off.
This exists so that &lt;em&gt;everyone&lt;/em&gt; can learn Rust and can get up to speed as quickly as possible.&lt;/p&gt;
&lt;p&gt;It isn't intended as a full, comprehensive course in Rust.
That's not doable in a condensed timeframe.
It's just intended to be a pragmatic introduction to get people going and to make it a whole lot less scary.&lt;/p&gt;
&lt;p&gt;🦀&lt;/p&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 13 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/introducing-yet-another-rust-resource-or-yarr/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Towards Fearless Macros</title><link>https://lambdaland.org/posts/2023-10-17_fearless_macros/</link><description>&lt;p&gt;Macros are tricky beasts. Most languages—if they have macros at all—usually include a huge “here there be dragons” warning to warn curious would-be macro programmers of the dangers that lurk ahead.&lt;/p&gt;
&lt;p&gt;What is it about macros that makes them so dangerous and unwieldy? That’s difficult to answer in general: there are many different macro systems with varying degrees of ease-of-use. Moreover, making macros easy to use safely is an open area of research—most languages that have macros don’t have features necessary to implement macros safely. Hence, most people steer clear of macros.&lt;/p&gt;
&lt;p&gt;There are many ways to characterize macro systems; I won’t attempt to cover them all here, but here’s the spectrum I’ll be covering in this post:&lt;/p&gt;
&lt;div class="figure"&gt;
  &lt;p&gt;&lt;img alt="" src="https://lambdaland.org/img/macro_spectrum.svg" /&gt;&lt;/p&gt;

  
  &lt;div class="figure-caption"&gt;
    &lt;span class="figure-caption-text"&gt;&lt;p&gt;A spectrum of how easy macro systems are to use safely&lt;/p&gt;
&lt;/span&gt;
  &lt;/div&gt;
  
&lt;/div&gt;
&lt;h2 id="c-macros-advanced-search-and-replace"&gt;C macros: advanced search-and-replace&lt;/h2&gt;
&lt;p&gt;If you’ve done any C programming, you’ve likely run into things like:&lt;/p&gt;
&lt;pre class="giallo" style="color: #D8DEE9; background-color: #2E3440;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #5E81AC; font-weight: bold;"&gt;#&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;define&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; FOO&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 42&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #88C0D0;"&gt;printf&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&amp;quot;&lt;/span&gt;&lt;span style="color: #A3BE8C;"&gt;The answer is: %s&lt;/span&gt;&lt;span style="color: #EBCB8B;"&gt;\n&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;&amp;quot;,&lt;/span&gt;&lt;span&gt; FOO&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;;&lt;/span&gt;&lt;span style="color: #616E88;"&gt;     /* prints &amp;quot;The answer is: 42&amp;quot; */&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That &lt;code&gt;#define&lt;/code&gt; bit is a macro—albeit a &lt;em&gt;C&lt;/em&gt; macro. These operate just after the lexer: they work on token streams. It’s a bit like textual search-and-replace, though it knows a &lt;em&gt;little&lt;/em&gt; bit about the structure of the language (not much: just what’s a token and what’s not) so that you won’t run into problems if you do something like this:&lt;/p&gt;
&lt;pre class="giallo" style="color: #D8DEE9; background-color: #2E3440;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #5E81AC; font-weight: bold;"&gt;#&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;define&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; FOO&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 42&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #88C0D0;"&gt;printf&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&amp;quot;&lt;/span&gt;&lt;span style="color: #A3BE8C;"&gt;It says FOO&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;&amp;quot;)&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;;&lt;/span&gt;&lt;span style="color: #616E88;"&gt;     /* prints &amp;quot;It says FOO&amp;quot; not &amp;quot;It says 42&amp;quot; */&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;because that &lt;code&gt;FOO&lt;/code&gt; in the string is &lt;em&gt;not&lt;/em&gt; a token—it’s just part of a string.&lt;/p&gt;
&lt;p&gt;C macros can’t do very much: you scan the token stream for a macro, then fill in the variables to the macro, and then replace the macro and the arguments its consumed with the filled-out template that is the macro definition. This prevents you from doing silly things like replacing something sitting inside of a string literal, but it’s far, far from being safe, as we’ll see in the next section.&lt;/p&gt;
&lt;h2 id="lisp-macros-operating-on-asts"&gt;Lisp macros: operating on ASTs&lt;/h2&gt;
&lt;p&gt;In contrast to C’s macros, Lisp’s macros are much more powerful. Lisp macros operate after the lexer &lt;em&gt;and&lt;/em&gt; the parser have had a go at the source code—Lisp macro operate on &lt;em&gt;abstract syntax trees&lt;/em&gt;—or ASTs, which is what the compiler or interpreter works with.&lt;/p&gt;
&lt;p&gt;Why is this a big deal? The ASTs capture the language’s semantics around precedence, for instance. In C you can write a macro that does unexpended things, like this:&lt;/p&gt;
&lt;pre class="giallo" style="color: #D8DEE9; background-color: #2E3440;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #5E81AC; font-weight: bold;"&gt;#&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;define&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; DOUBLE&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;span style="color: #5E81AC;"&gt; x &lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;+&lt;/span&gt;&lt;span style="color: #5E81AC;"&gt; x&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #B48EAD;"&gt;3&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; *&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; DOUBLE&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt;5&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;;&lt;/span&gt;&lt;span style="color: #616E88;"&gt; /* Does 3 * 5 + 5 = 20, not 3 * (5 + 5) = 30 */&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;DOUBLE&lt;/code&gt; macro didn’t know anything about precedence and we computed the wrong thing. This means that, to use a macro in C, you have to have a good idea of &lt;em&gt;how&lt;/em&gt; it’s doing what it’s intended to do. That means C macros are leaky abstractions that prevent local reasoning: you have to consider both the macro definition and where it’s used to understand what’s going on.&lt;/p&gt;
&lt;p&gt;In contrast, Lisp macros are an improvement because they will rewrite the AST and the precedence you’d expect will be preserved. You can do this, for example:&lt;/p&gt;
&lt;pre class="giallo" style="color: #D8DEE9; background-color: #2E3440;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;defmacro&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; double&lt;/span&gt;&lt;span&gt; (x) `(&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;+&lt;/span&gt;&lt;span&gt; ,x ,x))&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;*&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 3&lt;/span&gt;&lt;span&gt; (double &lt;/span&gt;&lt;span style="color: #B48EAD;"&gt;5&lt;/span&gt;&lt;span&gt;))                        &lt;/span&gt;&lt;span style="color: #616E88;"&gt;; returns 30&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lisp macros are also &lt;em&gt;procedural macros&lt;/em&gt;, meaning you can execute arbitrary code inside of a macro to generate new ASTs. Macros in Lisp and its descendants are essentially functions from AST → AST. This opens up a whole world of exciting possibilities! Procedural macros constitute a “lightweight compiler API”. [&lt;a href="https://lambdaland.org/posts/2023-10-17_fearless_macros/#citeproc_bib_item_4"&gt;4&lt;/a&gt;]&lt;/p&gt;
&lt;h2 id="scheme-macros-hygiene"&gt;Scheme macros: hygiene&lt;/h2&gt;
&lt;div class="marginnote"&gt;
&lt;p&gt;“Same except for variable names” is also called alpha-equivalence. This comes from the λ-calculus, which states that the particular choice of variable name should not matter. E.g. 
 and 
 are the same function in the lambda calculus, just as 
 and 
 are the same function in algebra.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Lisp macros aren’t without danger—many a Lisp programmer has shot their foot off with a macro. One reason is that Lisp macros are not &lt;em&gt;hygienic&lt;/em&gt;—variables in the macro’s implementation may leak into the context of the macro call. This means that two Lisp programs that are the same except for different variable names can behave differently:&lt;/p&gt;
&lt;pre class="giallo" style="color: #D8DEE9; background-color: #2E3440;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;(defmacro swap (x y)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  `(let ((tmp ,x))      ;(tmp-leaky)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;     (setq ,x ,y)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;     (setq ,y tmp)))&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;(let ((foo 1)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;      (bar 2))&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  (swap foo bar)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  (list foo bar)) ; produces '(2 1)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;(let ((tmp 1)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;      (baz 2))&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  (swap tmp baz)        ;(tmp-capture)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  (list tmp baz)) ; produces '(1 2) --- no swap!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The fact that the macro implementation uses a variable named &lt;code&gt;tmp&lt;/code&gt; (tmp-leaky) has leaked through to the user of the macro. (tmp-capture) This phenomenon is called &lt;em&gt;variable capture&lt;/em&gt;, and it exposes this macro as a leaky abstraction! There are ways to mitigate this using &lt;code&gt;gensym&lt;/code&gt;, but those are error-prone manual techniques. It makes macro writing feel like you’re writing in an unsafe lower-level language.&lt;/p&gt;
&lt;p&gt;Scheme’s macros introduce a concept known as &lt;em&gt;hygiene&lt;/em&gt;, which prevents variable capture automatically:&lt;/p&gt;
&lt;pre class="giallo" style="color: #D8DEE9; background-color: #2E3440;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;(define-syntax swap&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  (syntax-rules ()&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    ((swap a b)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;     (let ((tmp a))      ; (tmp-intro-macro)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;       (set! a b)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;       (set! b tmp)))))&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;(let ((foo 1)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;      (bar 2))&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  (swap foo bar)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  (list foo bar)) ; produces '(2 1)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;(let ((tmp 1)            ; (tmp-intro-let)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;      (baz 2))&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  (swap tmp baz)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  (list tmp baz)) ; still produces '(2 1)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case, the variable &lt;code&gt;tmp&lt;/code&gt; that the &lt;code&gt;swap&lt;/code&gt; macro introduces (tmp-intro-macro) is not the same thing that the variable &lt;code&gt;tmp&lt;/code&gt; from the calling context (tmp-intro-let) refers to. This separation of scopes happens automatically behind the scenes, so there’s now no chance of accidental variable capture.&lt;sup class="footnote-reference" id="fr-1-1"&gt;&lt;a href="https://lambdaland.org/posts/2023-10-17_fearless_macros/#fn-1"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;If you’d like to know more about hygiene, [&lt;a href="https://lambdaland.org/posts/2023-10-17_fearless_macros/#citeproc_bib_item_1"&gt;1&lt;/a&gt;] is an excellent resource.&lt;/p&gt;
&lt;h2 id="racket-macros-phase-separation-and-scope-sets"&gt;Racket macros: phase separation and scope sets&lt;/h2&gt;
&lt;p&gt;Since Scheme macros (and Lisp macros more generally) allow running arbitrary Scheme code—including code from other modules—the dependency graph between modules can get so tangled that clean builds of a Scheme codebase are impossible. Racket solves this problem with its &lt;a class="link-external" href="https://docs.racket-lang.org/guide/phases.html" rel="external"&gt;phase separation&lt;/a&gt;, which puts clear delimiters between when functions and macros are available to different parts of the language. This detangles dependency graphs without sacrificing the expressive power of macros. I wrote a little bit about &lt;a href="https://lambdaland.org/posts/2023-05-19_racket_macros/#phases"&gt;phase separation&lt;/a&gt;; you can read more on &lt;a class="link-external" href="https://docs.racket-lang.org/reference/syntax-model.html#%28tech._phase._level%29" rel="external"&gt;the Racket docs&lt;/a&gt; as well as Matthew Flatt’s paper [&lt;a href="https://lambdaland.org/posts/2023-10-17_fearless_macros/#citeproc_bib_item_3"&gt;3&lt;/a&gt;] on phase separation.&lt;/p&gt;
&lt;p&gt;Racket also has a robust system for reasoning about where a variable’s definition comes from called a &lt;em&gt;scope set&lt;/em&gt;. This is a notion makes reasoning about where variables are bound sensible. See a &lt;a class="link-external" href="https://www-old.cs.utah.edu/plt/scope-sets/" rel="external"&gt;blog post&lt;/a&gt; as well as [NO_ITEM_DATA:flattBindingSetsScopes2016b] by Matthew Flatt for more on scope sets.&lt;/p&gt;
&lt;p&gt;Phase separation and scope sets make Racket macros the safest to use: Racket macros compose sensibly and hide their implementation details so that it is easy to write macros that are easy to use as if they were built-in language constructs.&lt;/p&gt;
&lt;p&gt;Racket also goes beyond the &lt;code&gt;syntax-rules&lt;/code&gt; form that it inherited from Scheme; Racket’s &lt;code&gt;syntax-parse&lt;/code&gt; macro-building system makes generating good error messages easy.&lt;/p&gt;
&lt;p&gt;There’s a little bug in the &lt;code&gt;swap&lt;/code&gt; macro we used earlier, and that is the &lt;code&gt;set!&lt;/code&gt; form only takes an identifier (i.e. a variable) as its first argument. We don’t have any error checking inside the macro; if we were to call &lt;code&gt;swap&lt;/code&gt; with something that wasn’t an identifier, we’d get an error in terms of the &lt;code&gt;set!&lt;/code&gt; the macro expands to, not the macro call itself:&lt;/p&gt;
&lt;pre class="giallo" style="color: #D8DEE9; background-color: #2E3440;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;let&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; ((&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;foo&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;      (&lt;/span&gt;&lt;span&gt;bar &lt;/span&gt;&lt;span style="color: #B48EAD;"&gt;2&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;  (&lt;/span&gt;&lt;span&gt;swap &lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;+&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; foo&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;span&gt; bar&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;  (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;list foo&lt;/span&gt;&lt;span&gt; bar&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span&gt;swap &lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;+&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; foo&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;span&gt; bar&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  set!: &lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;not&lt;/span&gt;&lt;span&gt; an identifier&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  at: &lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;+&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; foo&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  in: &lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;set!&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;+&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; foo&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;span&gt; bar&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This isn’t good because there’s no &lt;code&gt;set!&lt;/code&gt; in our code at all! We could add some error handling in our macro to manually check that &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; are identifiers, but that’s a little tedious. Racket’s &lt;code&gt;syntax-parse&lt;/code&gt; helps us out:&lt;/p&gt;
&lt;pre class="giallo" style="color: #D8DEE9; background-color: #2E3440;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;require&lt;/span&gt;&lt;span&gt; syntax/parse/define&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;define-syntax&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;swap&lt;/span&gt;&lt;span&gt; stx&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;  (&lt;/span&gt;&lt;span&gt;syntax-parse stx&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;    [(&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;_&lt;/span&gt;&lt;span&gt; a:id b:id&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #88C0D0;"&gt;     #'&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;let&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; ([&lt;/span&gt;&lt;span&gt;tmp a&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;         (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;set!&lt;/span&gt;&lt;span&gt; a b&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;         (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;set!&lt;/span&gt;&lt;span&gt; b tmp&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;))]))&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;let&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; ([&lt;/span&gt;&lt;span&gt;tmp&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;      [&lt;/span&gt;&lt;span&gt;baz&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 2&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;  (&lt;/span&gt;&lt;span&gt;swap tmp baz&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;  (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;list&lt;/span&gt;&lt;span&gt; tmp baz&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;))&lt;/span&gt;&lt;span style="color: #616E88;"&gt;            ; returns '(2 1) as expected&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;let&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; ([&lt;/span&gt;&lt;span&gt;foo&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;      [&lt;/span&gt;&lt;span&gt;bar&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 2&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;  (&lt;/span&gt;&lt;span&gt;swap&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;+&lt;/span&gt;&lt;span&gt; foo&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;span&gt; bar&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;swap: expected identifier&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  at:&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;+&lt;/span&gt;&lt;span&gt; foo&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  in:&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;swap&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;+&lt;/span&gt;&lt;span&gt; foo&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;span&gt; bar&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Much better! Now our error is in terms that the macro user will recognize. There are lots of other things that &lt;code&gt;syntax-parse&lt;/code&gt; can do that make it easy to write correct macros that generate good error messages—a must for macros that become a part of a library.&lt;/p&gt;
&lt;h2 id="other-languages"&gt;Other languages&lt;/h2&gt;
&lt;p&gt;Many modern languages use macros; I’ll only talk about a few more here. If something’s missing, that’s probably because I didn’t want to be exhaustive.&lt;/p&gt;
&lt;h3 id="julia"&gt;Julia&lt;/h3&gt;
&lt;p&gt;Julia macros have a lot of nice things: they operate on ASTs and they’re hygienic, though the way hygiene is currently implemented is a little strange: all variables get &lt;code&gt;gensym&lt;/code&gt;’d automatically{{% sidenote sn2 %}}Meaning, they all get replaced with some generated symbol that won’t clash with any possible variable or function name.{{% /sidenote %}} whether or not they come from inside the macro or they originated from the calling code.&lt;/p&gt;
&lt;p&gt;Part of the problem is that all variables are represented as simple symbols, which [&lt;a href="https://lambdaland.org/posts/2023-10-17_fearless_macros/#citeproc_bib_item_1"&gt;1&lt;/a&gt;] shows is insufficient to properly implement hygiene.&lt;/p&gt;
&lt;p&gt;Evidently there is &lt;a class="link-external" href="https://github.com/JuliaLang/julia/pull/6910" rel="external"&gt;some&lt;/a&gt; &lt;a class="link-external" href="https://github.com/JuliaLang/julia/issues/37691" rel="external"&gt;ongoing&lt;/a&gt; &lt;a class="link-external" href="https://github.com/JuliaLang/JuliaSyntax.jl/pull/329" rel="external"&gt;work&lt;/a&gt; to improve the situation. This is a good example of research ideas percolating into industry languages I think.&lt;/p&gt;
&lt;h3 id="elixir"&gt;Elixir&lt;/h3&gt;
&lt;p&gt;Elixir has robust AST macros, and its standard library makes heavy use of macros; many “core” Elixir constructs like &lt;code&gt;def&lt;/code&gt;, &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;raise&lt;/code&gt;, &lt;code&gt;|&amp;gt;&lt;/code&gt;, and others are actually macros that expand to smaller units of Elixir.&lt;/p&gt;
&lt;p&gt;Elixir actually gets hygiene right! Unlike Julia, variables in Elixir’s AST have metadata—including scope information—attached to them. This and other aspects of Elixir’s macro system open it up to lots of exciting possibilities. The &lt;a class="link-external" href="https://hexdocs.pm/nx/Nx.html" rel="external"&gt;Nx&lt;/a&gt; library brings support for numerical and GPU programming to Elixir, and it works essentially by implementing a custom Elixir compiler &lt;em&gt;in Elixir itself&lt;/em&gt;, and macros play a big role in this.&lt;/p&gt;
&lt;div class="marginnote"&gt;
&lt;p&gt;Me thinking that Elixir is a big mainstream language should tell you something about the languages I spend my time with in my job as a PhD student.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;I think Elixir macros are really neat—they’re the most powerful I’ve seen in a “big mainstream” language.&lt;/p&gt;
&lt;h3 id="rust"&gt;Rust&lt;/h3&gt;
&lt;p&gt;Rust supports two kinds of macros: macros-by-example, and procedural macros.&lt;/p&gt;
&lt;p&gt;Macros-by-example are a simple pattern-to-pattern transformation. Here’s an example from &lt;a class="link-external" href="https://doc.rust-lang.org/book/ch19-06-macros.html" rel="external"&gt;The Rust Book&lt;/a&gt;:&lt;/p&gt;
&lt;pre class="giallo" style="color: #D8DEE9; background-color: #2E3440;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #5E81AC;"&gt;#[macro_export]&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #88C0D0; font-weight: bold;"&gt;macro_rules! vec&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;    (&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; $&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; $&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;:&lt;/span&gt;&lt;span&gt;expr&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; ),&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;*&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; )&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; =&amp;gt;&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; {&lt;/span&gt;&lt;span style="color: #616E88;"&gt;     // (pattern-repeat)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;            let mut&lt;/span&gt;&lt;span&gt; temp_vec&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; =&lt;/span&gt;&lt;span style="color: #8FBCBB;"&gt; Vec&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;::&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;new&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;            $&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;                temp_vec&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;.&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;push&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;$&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;            )&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;*&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;            temp_vec&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This macro takes a pattern like&lt;/p&gt;
&lt;pre class="giallo" style="color: #D8DEE9; background-color: #2E3440;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #88C0D0; font-weight: bold;"&gt;vec!&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span&gt;foo&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;,&lt;/span&gt;&lt;span&gt; bar baz&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and expands it to a pattern like&lt;/p&gt;
&lt;pre class="giallo" style="color: #D8DEE9; background-color: #2E3440;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #81A1C1;"&gt;    let mut&lt;/span&gt;&lt;span&gt; temp_vec&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt; =&lt;/span&gt;&lt;span style="color: #8FBCBB;"&gt; Vec&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;::&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;new&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    temp_vec&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;.&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;push&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span&gt;foo&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    temp_vec&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;.&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;push&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span&gt;bar&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    temp_vec&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;.&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;push&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span&gt;baz&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    temp_vec&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice how the &lt;code&gt;*&lt;/code&gt; marks a part of the template that can be repeated. (pattern-repeat) This is akin to Racket or Scheme’s &lt;code&gt;...&lt;/code&gt; repetition form. Macros-by-example work on AST, but you can’t perform arbitrary computation on the AST. For that, you need procedural macros.&lt;/p&gt;
&lt;p&gt;Rust’s procedural macros (called “proc macros”) work on a token stream, and you can perform arbitrary computation, which puts them in a bit of a funny middle ground between C and Lisp. There is a &lt;a class="link-external" href="https://docs.rs/syn/2.0.39/syn/" rel="external"&gt;Rust crate&lt;/a&gt; that you can use to parse a Rust token stream into Rust AST, but you don’t get any nice source information from the AST nodes, which makes producing good error messages a challenge.&lt;/p&gt;
&lt;p&gt;I personally find Rust macros to be disappointing.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;There’s a wide variety of macro systems. The best macro systems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Operate on the AST&lt;/em&gt; rather than on a stream of tokens&lt;/li&gt;
&lt;li&gt;Avoid leaking implementation details through inadvertent variable capture by being &lt;em&gt;hygienic&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Produce good error messages that are in &lt;em&gt;terms of the caller’s context&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;(Bonus) have good &lt;em&gt;phase separation&lt;/em&gt; to enforce clear separation between complex macro systems&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Different languages have different features in their macro systems; some languages make it easy to use macros sensibly, while for others macros are a formidable challenge to use properly—make sure you know what your language provides and the trade-offs involved.&lt;/p&gt;
&lt;h3 id="why-shouldn-t-you-use-macros"&gt;Why shouldn’t you use macros?&lt;/h3&gt;
&lt;p&gt;Turns out you can do a &lt;em&gt;lot&lt;/em&gt; with functions. Powerful function programming languages let you do so much with first-class functions. If you can get access to &lt;a href="https://lambdaland.org/posts/2022-11-17_continutations/"&gt;first-class continuations&lt;/a&gt;, as you can in Racket and Scheme, then you can create powerful new programming constructs without having to resort to macros.&lt;/p&gt;
&lt;p&gt;I came across the &lt;a class="link-external" href="https://www.youtube.com/watch?v=mSgXWpvQEHE&amp;amp;t=579s" rel="external"&gt;JuliaCon 2019 keynote&lt;/a&gt; talk, where Steven Johnson explains how many of the things that you can do with macros can be solved just with Julia’s type dispatch.&lt;/p&gt;
&lt;p&gt;If you can do something with functions, you probably should: functions are first-class values in most languages these days, and you’ll enjoy increased composability, better error messages, and code that is easier to read and understand by your peers.&lt;/p&gt;
&lt;p&gt;Macros introduce little languages wherever you use them. For simple macros, you might not have &lt;em&gt;any&lt;/em&gt; constraints on what you may write under the scope of a macro. As an example, consider a macro that adds a &lt;code&gt;while&lt;/code&gt;-loop construct to a language by rewriting to another kind of looping mechanism: you shouldn’t have any restriction on what you can write inside the body of the &lt;code&gt;while&lt;/code&gt; loop.&lt;/p&gt;
&lt;p&gt;However, more complex macros can impose more restrictions on what can and cannot be written under their lexical extent. These restrictions may or may not be obvious. Examples: accidental variable capture limits what can be safely written, and grammatical errors (e.g. using an expression where an identifier was expected) can lead to inscrutable errors.&lt;/p&gt;
&lt;p&gt;Better macro systems mitigate these problems. It’s not enough to just have a macro system that uses ASTs; you need a macro system that makes it easy to write &lt;em&gt;correct&lt;/em&gt; macros with clear error messages so they truly feel like natural extensions of the language. Few languages do this right.&lt;/p&gt;
&lt;h3 id="why-should-you-use-macros"&gt;Why should you use macros?&lt;/h3&gt;
&lt;p&gt;Macro systems have improved since the 1960s. While Lisp excluded many of the pitfalls of C macros &lt;em&gt;by construction&lt;/em&gt;, you still had to use kluges like &lt;code&gt;gensym&lt;/code&gt; to manually avoid variable capture. Scheme got rid of that with hygienic macros, and Racket improved matters further by improving macro hygiene through scope sets and introducing phase separation. It is so much easier to build robust macro-based abstractions.&lt;/p&gt;
&lt;p&gt;Macros are good—anyone can write macros and experiment with new syntactic constructs. This makes development and extension of the language no longer the sole domain of the language designer and maintainer—library authors can experiment with different approaches to various problems.&lt;/p&gt;
&lt;p&gt;We see this a lot with Elixir: Elixir’s core language is really rather small; most of the magic powering popular libraries like &lt;a class="link-external" href="https://hexdocs.pm/ecto/getting-started.html" rel="external"&gt;Ecto&lt;/a&gt; or &lt;a class="link-external" href="https://www.phoenixframework.org/" rel="external"&gt;Phoenix&lt;/a&gt; comes from a choice set of macro abstractions. These and other libraries are free to experiment with novel syntax without fear of cluttering and coupling the core language with bad abstractions that would then need to be maintained in perpetuity.&lt;/p&gt;
&lt;p&gt;Macros can be powerful when used correctly—something made much easier by modern macro systems.&lt;/p&gt;
&lt;h2 id="references"&gt;References&lt;/h2&gt;
&lt;div class="csl-bib-body"&gt;
  &lt;div class="csl-entry"&gt;&lt;a id="citeproc_bib_item_1"&gt;&lt;/a&gt;
    &lt;div class="csl-left-margin"&gt;[1]&lt;/div&gt;&lt;div class="csl-right-inline"&gt;Adams, M.D. 2015. &lt;a href="https://doi.org/10.1145/2676726.2677013"&gt;Towards the Essence of Hygiene&lt;/a&gt;. &lt;i&gt;Proceedings of the 42nd Annual ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages&lt;/i&gt; (Mumbai India, Jan. 2015), 457–469.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="csl-entry"&gt;&lt;a id="citeproc_bib_item_2"&gt;&lt;/a&gt;
    &lt;div class="csl-left-margin"&gt;[2]&lt;/div&gt;&lt;div class="csl-right-inline"&gt;Barzilay, E., Culpepper, R. and Flatt, M. Keeping it Clean with Syntax Parameters.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="csl-entry"&gt;&lt;a id="citeproc_bib_item_3"&gt;&lt;/a&gt;
    &lt;div class="csl-left-margin"&gt;[3]&lt;/div&gt;&lt;div class="csl-right-inline"&gt;Flatt, M. 2002. &lt;a href="https://doi.org/10.1145/581478.581486"&gt;Composable and compilable macros: You want it when?&lt;/a&gt; &lt;i&gt;Proceedings of the seventh ACM SIGPLAN international conference on Functional programming&lt;/i&gt; (New York, NY, USA, Sep. 2002), 72–83.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="csl-entry"&gt;&lt;a id="citeproc_bib_item_4"&gt;&lt;/a&gt;
    &lt;div class="csl-left-margin"&gt;[4]&lt;/div&gt;&lt;div class="csl-right-inline"&gt;Krishnamurthi, S. 2006. EDUCATIONAL PEARL: Automata via macros. &lt;i&gt;Journal of functional programming&lt;/i&gt;. 16, 03 (May 2006), 253. DOI:&lt;a href="https://doi.org/10.1017/S0956796805005733"&gt;https://doi.org/10.1017/S0956796805005733&lt;/a&gt;.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="csl-entry"&gt;NO_ITEM_DATA:flattBindingSetsScopes2016b&lt;/div&gt;
&lt;/div&gt;
&lt;section class="footnotes"&gt;
&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-1"&gt;
&lt;p&gt;Breaking hygiene has some utility in some cases—for example, one might want to add a &lt;code&gt;break&lt;/code&gt; form inside the body of a loop. There are ways around hygiene, but these are not without some problems. For more details see [&lt;a href="https://lambdaland.org/posts/2023-10-17_fearless_macros/#citeproc_bib_item_2"&gt;2&lt;/a&gt;]. &lt;a href="https://lambdaland.org/posts/2023-10-17_fearless_macros/#fr-1-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</description><author>Lambda Land</author><pubDate>Mon, 13 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://lambdaland.org/posts/2023-10-17_fearless_macros/</guid></item><item><title>A short bleep...</title><link>https://holzer.online/weeknotes/2023/45/index.html</link><description>&lt;time datetime="2023-11-12 22:00"&gt;&lt;/time&gt;

&lt;p&gt;
As my family and I are looking forward to move into a new home in January, the next weeks will be filled by throwing out junk that accumulated over the years, packing up our belongings into boxes, looking for new furniture etc.pp. and I'll likely be hard-pressed for time to write — therefore, as a short bleep before I go radio silent, I want to cluster some ideas that I'd like to pick up, once I have a little more time at my disposal again. 
&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;
I'd like to finish a draft about the implementation of the little web component that &lt;a href="/weeknotes/2023/43/"&gt;I mentioned previously&lt;/a&gt;. This one has the strongest chance to still get done this year.
&lt;/li&gt;

&lt;li&gt;
There's a pile of scattered notes on microfrontend architectures, that have accumulated over the nearly eight years that I'm working on that topic professionally. I'd like to consolidate them with my personal experience and opinions on it. As that might get out of hand and maybe deserves a longer treatment, I'm thinking about experimenting with some kind of preplanned article series.
&lt;/li&gt;

&lt;li&gt;
Earlier this year I mused about the &lt;a href="/articles/2023/06/27/se-great-books-curriculum/"&gt;great books of software engineering&lt;/a&gt;, but the article was a bit unconclusive. I will have to pack up my library soon and thought, when I unpack the books, I'd like to do some reviews and summaries on a selected few items.
&lt;/li&gt;

&lt;li&gt;
Then, and this one is a bit of a moonshot topic, earlier this year, I took some interest in the current state of rapid application development. I'm still occasionally asking myself why, a quater of a century after the final release of Visual Basic 6, nothing has emerged that lives up to its developer experience, when it comes to making UIs and wrapping them up with a datasource. I have a some hypotheses about that, though...
&lt;/li&gt;

&lt;li&gt;
The quality of the major search engines have deteriorated over the last years to the point where their results are basically garbage. The rise of LLMs has, if anything, accelerated their enshittification. Plus ça change, plus c'est la même chose... I'd like to dive into the topic of curated web directories and explore whether there are lessons to be learned from the demise of Yahoo and DMOZ and if smaller directories could somehow become (again) a backbone for finding information on the web.
&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;
If any of you'd like to talk about such stuff, feel free to shoot me a &lt;a href="/contact.html"&gt;mail&lt;/a&gt;. Until next time, take good care :).  
&lt;/p&gt;</description><author>Fabian's public notepad</author><pubDate>Sun, 12 Nov 2023 23:00:00 GMT</pubDate><guid isPermaLink="true">https://holzer.online/weeknotes/2023/45/index.html</guid></item><item><title>The most commonly imported module</title><link>https://thatxliner.github.io/posts/the-most-commonly-imported-module/</link><description>Not all modules are created equal</description><author>ThatXliner's Blog</author><pubDate>Sun, 12 Nov 2023 18:09:22 GMT</pubDate><guid isPermaLink="true">https://thatxliner.github.io/posts/the-most-commonly-imported-module/</guid></item><item><title>The Lean Startup</title><link>https://olshansky.info/book/the_lean_startup/</link><description>Olshansky's review of The Lean Startup</description><author>🦉 olshansky 🦁</author><pubDate>Sun, 12 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/book/the_lean_startup/</guid></item><item><title>Unix Stackexchange Tags</title><link>https://vit.baisa.cz/notes/code/unix-stackexchange-tags/</link><description>a list of tags from unix.stackexchange.com</description><author>Vít Baisa</author><pubDate>Sun, 12 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://vit.baisa.cz/notes/code/unix-stackexchange-tags/</guid></item><item><title>Town Hall #15: Artifacts</title><link>https://taylor.town/town-hall-0015</link><description>101,721 words of cryptic nonsense.</description><author>taylor.town</author><pubDate>Sun, 12 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/town-hall-0015</guid></item><item><title>Home DNS with Unbound and NSD</title><link>https://skife.org/b4/unbound-and-nsd/</link><description>&lt;p&gt;I recently redid the DNS on my home network, moving from &lt;a href="https://dnsmasq.org" rel="external"&gt;dnsmasq&lt;/a&gt; to &lt;a href="https://nlnetlabs.nl/projects/unbound/about/" rel="external"&gt;Unbound&lt;/a&gt;
and &lt;a href="https://nlnetlabs.nl/projects/nsd/about/" rel="external"&gt;NSD&lt;/a&gt;. Unbound acts as the DNS server the network uses, and Unbound hosts losts local zones for my search domains. For reasons, this is really across two sites: our home, and a barn we own a few miles away. Because I am a dork, I have things at both sites :-)&lt;/p&gt;
&lt;p&gt;My network controller has a built in DNS server which assigns a local domain, a la &lt;code&gt;brians-laptop.local&lt;/code&gt; to anything which gets a DHCP lease. I wanted to be able to respect these, but also assign a diferent domain to statically assigned things on the network, such as printers and our NAS. For these I set up a &lt;code&gt;.home&lt;/code&gt; and &lt;code&gt;.barn&lt;/code&gt; respectively, for things in the home and in the barn. Those zones are hosted on NSD, with a config like:&lt;/p&gt;
&lt;pre class="giallo" style="color: #24292E; background-color: #FFFFFF;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #6A737D;"&gt;# /usr/local/etc/nsd/nsd.conf&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #22863A;"&gt;server&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #22863A;"&gt;    ip-address&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #005CC5;"&gt; 127.0.0.1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #22863A;"&gt;    port&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #005CC5;"&gt; 53530&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #22863A;"&gt;zone&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #22863A;"&gt;    name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #032F62;"&gt; home&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #22863A;"&gt;    zonefile&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #032F62;"&gt; &amp;quot;home.zone&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #22863A;"&gt;zone&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #22863A;"&gt;    name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #032F62;"&gt; barn&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #22863A;"&gt;    zonefile&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #032F62;"&gt; &amp;quot;barn.zone&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that NSD is only listening on &lt;code&gt;127.0.0.1&lt;/code&gt; and on port 53530. It should only ever be queried from the unbound instance on the same host (which is using port 53).&lt;/p&gt;
&lt;p&gt;The zone files referenced are just stubs, not really correct, but they don't need to be :-)&lt;/p&gt;
&lt;pre class="giallo" style="color: #24292E; background-color: #FFFFFF;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #6A737D;"&gt;; /usr/local/etc/nsd/home.zone&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;$ORIGIN home. &lt;/span&gt;&lt;span style="color: #6A737D;"&gt;; 'default' domain as FQDN for this zone&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;$TTL &lt;/span&gt;&lt;span style="color: #005CC5;"&gt;600&lt;/span&gt;&lt;span style="color: #6A737D;"&gt; ; default time-to-live for this zone&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;home.   IN  SOA     ns.home. noc.dns.icann.org. (&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #005CC5;"&gt;        16&lt;/span&gt;&lt;span style="color: #6A737D;"&gt;          ;Serial&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #005CC5;"&gt;        7200&lt;/span&gt;&lt;span style="color: #6A737D;"&gt;        ;Refresh&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #005CC5;"&gt;        3600&lt;/span&gt;&lt;span style="color: #6A737D;"&gt;        ;Retry&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #005CC5;"&gt;        1209600&lt;/span&gt;&lt;span style="color: #6A737D;"&gt;     ;Expire&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #005CC5;"&gt;        3600&lt;/span&gt;&lt;span style="color: #6A737D;"&gt;        ;Negative response caching TTL&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #6A737D;"&gt;; The nameserver that are authoritative for this zone.&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;                        NS      ns.home.&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;nas.home.       A       192.168.2.114&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;printer.home.   A       192.168.2.140&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;m0001.home.     A       192.168.2.101&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;m0002.home.     A       192.168.2.102&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and the barn:&lt;/p&gt;
&lt;pre class="giallo" style="color: #24292E; background-color: #FFFFFF;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;$ORIGIN barn. &lt;/span&gt;&lt;span style="color: #6A737D;"&gt;; 'default' domain as FQDN for this zone&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;$TTL &lt;/span&gt;&lt;span style="color: #005CC5;"&gt;600&lt;/span&gt;&lt;span style="color: #6A737D;"&gt; ; default time-to-live for this zone&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;barn.   IN  SOA     ns.barn. noc.dns.icann.org. (&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #005CC5;"&gt;        17&lt;/span&gt;&lt;span style="color: #6A737D;"&gt;          ;Serial&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #005CC5;"&gt;        7200&lt;/span&gt;&lt;span style="color: #6A737D;"&gt;        ;Refresh&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #005CC5;"&gt;        3600&lt;/span&gt;&lt;span style="color: #6A737D;"&gt;        ;Retry&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #005CC5;"&gt;        1209600&lt;/span&gt;&lt;span style="color: #6A737D;"&gt;     ;Expire&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #005CC5;"&gt;        3600&lt;/span&gt;&lt;span style="color: #6A737D;"&gt;        ;Negative response caching TTL&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #6A737D;"&gt;; The nameserver that are authoritative for this zone.&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;                NS      ns.barn.&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;m0003.barn.     A       192.168.81.101&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;m0004.barn.     A       192.168.81.102&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;dvr.barn.       A       192.168.81.110&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With NSD up and running, I set up Unbound to make use of those local zones. It runs on the same instance as NSD, and is configured ot use it for those domains:&lt;/p&gt;
&lt;pre class="giallo" style="color: #24292E; background-color: #FFFFFF;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #6A737D;"&gt;# /usr/local/etc/unbound/unbound.conf&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #22863A;"&gt;server&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #6A737D;"&gt;    # This is a CARP interface, which apparently &lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #6A737D;"&gt;    # requires explicitely listing&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #22863A;"&gt;    interface&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #005CC5;"&gt; 192.168.2.100&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #22863A;"&gt;    interface&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #005CC5;"&gt; 0.0.0.0&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #22863A;"&gt;    do-not-query-localhost&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #005CC5;"&gt; no&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #22863A;"&gt;    access-control&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #032F62;"&gt; 192.168.0.0/16 allow&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #22863A;"&gt;    local-zone&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #032F62;"&gt; &amp;quot;home&amp;quot; nodefault&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #22863A;"&gt;    domain-insecure&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #032F62;"&gt; &amp;quot;home&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #22863A;"&gt;    local-zone&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #032F62;"&gt; &amp;quot;barn&amp;quot; nodefault&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #22863A;"&gt;    domain-insecure&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #032F62;"&gt; &amp;quot;barn&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #22863A;"&gt;stub-zone&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #22863A;"&gt;    name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #032F62;"&gt; &amp;quot;home.&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #22863A;"&gt;    stub-addr&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #032F62;"&gt; 127.0.0.1@53530&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #22863A;"&gt;stub-zone&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #22863A;"&gt;    name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #032F62;"&gt; &amp;quot;barn.&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #22863A;"&gt;    stub-addr&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #032F62;"&gt; 127.0.0.1@53530&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #22863A;"&gt;forward-zone&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #22863A;"&gt;    name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #032F62;"&gt; &amp;quot;local.&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #22863A;"&gt;    forward-addr&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #005CC5;"&gt; 192.168.1.1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unbound is set up to listen on all interfaces, but because I am using a &lt;a href="https://man.freebsd.org/cgi/man.cgi?query=carp&amp;amp;sektion=4" rel="external"&gt;CARP&lt;/a&gt; interface as well, it seems to require seperately listing that one to avoid confusions. The two local zones are configured to be insecure (no DNSSEC) and to forward to the NSD instance as a &lt;code&gt;stub-zone&lt;/code&gt; for each domain, respectively.&lt;/p&gt;
&lt;p&gt;The dynamically assigned &lt;code&gt;.local&lt;/code&gt; domain is forwarded to the router to pick up the names of things which get DHCP leases via the &lt;code&gt;forward-zone&lt;/code&gt; block.&lt;/p&gt;
&lt;p&gt;Finally, to make it all work, I hand out three search domains on DHCP, &lt;code&gt;.local&lt;/code&gt;, &lt;code&gt;.home&lt;/code&gt;, and &lt;code&gt;.barn&lt;/code&gt;. This is DHCP code 119 with a value &lt;code&gt;local,home,barn&lt;/code&gt;, for future reference.&lt;/p&gt;
&lt;p&gt;Deployment wise, this is running on two servers in each location, with each server running both NSD and Unbound. The CARP interface is used to provide a single IP address for the DNS servers, and the DHCP server is configured to hand out that IP address as the DNS server for the network.&lt;/p&gt;
&lt;p&gt;Fun fact along the way: I learned emacs has a mode for zone files which automatically increments &lt;code&gt;serial&lt;/code&gt; when you save. Handy!&lt;/p&gt;</description><author>Brian's Waste of Time</author><pubDate>Sun, 12 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://skife.org/b4/unbound-and-nsd/</guid></item><item><title>Making Transmitic Faster...By Doing Less!</title><link>https://andrewshay.me/blog/making-transmitic-faster</link><description>&lt;h2&gt;What is Transmitic &amp; The Prototype Phase&lt;/h2&gt;
&lt;a href="https://github.com/transmitic/transmitic"&gt;Transmitic&lt;/a&gt; is an encrypted, p2p, file transfer and sharing program.  I created it because, while there are many file transfer programs out there, there was nothing quite like Transmitic.  &lt;br /&gt;
The main difference is in the UI.  You add users you want to share with and which files and folders each user can download from you.  Then users can download those files whenever they want.  Completely p2p.&lt;br /&gt;
&lt;br /&gt;
A few years ago, I wanted to learn a new programming language and decided on Rust.  I then decided my first project with Rust should be Transmitic.  &lt;br /&gt;
Learning Rust was quite difficult at first, and when combined with creating a new p2p application, it was a lot to try and handle after work.  &lt;br /&gt;
Therefore, I could not try to optimize, cache, or really architect anything.  &lt;br /&gt;
In order to not get overwhelmed by the project, I decided that I would just learn, and code, little bits at a time, without being concerned about code quality (except for the crypto) or performance.  &lt;br /&gt;
&lt;br /&gt;
This worked out well because I have been able to get Transmitic to a very functional (yet still Beta) state.  It now includes all the primary features I wanted it to have.  &lt;br /&gt;
There is still much more I want to do though.  &lt;br /&gt;
&lt;br /&gt;
Until very recently, it was really more in an alpha/prototype state. There was wasted bandwidth and things that could be cached, but weren't.  &lt;br /&gt;
I will discuss some of the enhancements that helped make Transmitic faster and use less bandwidth, which can be summarized as "do less!"  &lt;br /&gt;
&lt;br /&gt;
None of this is novel. It's all obvious, and if you're able to truly focus on a project and plan it, you won't hit these.  &lt;br /&gt;
I just couldn't think about it at the time.  Once the feature worked, ship it!  &lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Overview of Features&lt;/h2&gt;
A quick overview of the features that will be discussed.  &lt;br /&gt;
&lt;ol&gt;
&lt;li&gt; The protocol is simple. It sends u16 messages to instruct the action to be performed, with a u32 payload size, followed by the payload itself.&lt;/li&gt;
&lt;li&gt; File Listings are JSON strings. They contain all file paths, and their sizes, that are shared with an individual user.  &lt;/li&gt;
&lt;li&gt; To determine when a file download is complete, the client sends the file path and current file size on disk to the server. The server sends more chunks of data or a download complete message.  &lt;/li&gt;
&lt;li&gt; "Reverse Connection" is a feature, that when enabled, Transmitic will periodically connect to each user (outgoing) to establish a connection for sharing. This feature is for users with networking limitations that cannot accept incoming connections. &lt;/li&gt; 
&lt;/ol&gt;
&lt;h2&gt;Cache File Listings&lt;/h2&gt;
When a user refreshes files that are shared with them, they download JSON data containing the file paths shared with them and the file sizes.  &lt;br /&gt;
This is the "File Listing".  &lt;br /&gt;
Initially, when a user started downloading files, the file listing was requested &lt;i&gt;again&lt;/i&gt;. This ensured they had the latest, valid, list of files shared with them.  &lt;br /&gt;
The client could then determine locally if the file was invalid or not (of course, the server would still reject any invalid files that were requested).&lt;br /&gt;
However, this is wasted bandwidth. Especially if the file listing was large, like 50MB. A user downloads 50MB worth of JSON to see what's available, and once they start downloading, they immediately download 50MB again. &lt;br /&gt;
Wasteful!  &lt;br /&gt;
&lt;br /&gt;
The same scenario occurred with the Reverse Connection feature.  &lt;br /&gt;
A user with Reverse Connection enabled, periodically connects to everyone they share with, and "hands off" that connection to allow file listings and downloads to occur.  &lt;br /&gt;
Because the File Listing was not cached, every Reverse Connection would trigger the file listing to be downloaded.  &lt;br /&gt;
Therefore, if you were sharing with anyone who used Reverse Connection, they would continually trigger your client to download the File Listing even if you already had the latest File Listing!  &lt;br /&gt;
Wasteful!  &lt;br /&gt;
&lt;br /&gt;
&lt;i&gt;Let's cache it!&lt;/i&gt;&lt;br /&gt;
&lt;br /&gt;
Reducing this waste is simple. We just need to cache the File Listing, ensure the appropriate threads have access to it, and determine when we need a new copy.  &lt;br /&gt;
&lt;br /&gt;
When a client starts downloading files, instead of requesting a new File Listing immediately, first it checks if it already has one. If it does, it attempts to download the file.  &lt;br /&gt;
Now, it will only get a new File Listing if the server responds with the file choice being "Invalid" i.e. Not shared with you.  &lt;br /&gt;
&lt;br /&gt;
The Reverse Connection logic now maintains a list of users who have the latest File Listing. This list is reset when the user changes which files they're sharing.  &lt;br /&gt;
When the Reverse Connection is established, there is now a new message that can be sent, &lt;code&gt;MSG_REVERSE_NO_LIST&lt;/code&gt;.  &lt;br /&gt;
This tells the client a Reverse Connection is being established, but the server thinks you have the latest File Listing.  &lt;br /&gt;
The client does not &lt;i&gt;need&lt;/i&gt; to request a new one.  &lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;If the Client Can Do It Alone, Do It Alone&lt;/h2&gt;
When a user downloads a directory, the client loops through every sub file in the File Listing and requests it from the server.  &lt;br /&gt;
If the download is interrupted, it can continue where it left off.  &lt;br /&gt;
It would NOT &lt;i&gt;redownload&lt;/i&gt; every file, but it would start from the first file in the directory and loop through them all again.  &lt;br /&gt;
Originally, the logic was simple. The next file is pulled from the file listing, check its size on disk, and send it to the server which would respond.  &lt;br /&gt;
This would happen for files that were already finished. The client would tell the server the current size and the server would respond, &lt;i&gt;download finished&lt;/i&gt;.  &lt;br /&gt;
Wasteful!  &lt;br /&gt;
&lt;br /&gt;
We have the File Listing locally, which already contains the expected file sizes!  &lt;br /&gt;
Now, when the client loops through the File Listing again, if the file size on disk matches the File Listing, it just moves onto the next file.  &lt;br /&gt;
The client does &lt;i&gt;not&lt;/i&gt; contact the server for finished files anymore!  &lt;br /&gt;
&lt;br /&gt;
After that change, a bug was introduced.  I noticed it when transferring some test directories.  &lt;br /&gt;
&lt;i&gt;0 byte files.&lt;/i&gt;  &lt;br /&gt;
When getting ready to process a file for download, the client sets the current size on disk, but if the file does not exist, it sets it to 0.  &lt;br /&gt;
That, combined with "if the size on disk matches the File Listing, move on to the next file", meant 0 byte files were not being created anymore. Oops!  &lt;br /&gt;
That was an easy fix though. If the File Listing says a file's size is 0 bytes, the client just creates the file locally. There is no need to ask the server anything!  &lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Redundant Server Messages&lt;/h2&gt;
Originally, the server would send a &lt;code&gt;MSG_FILE_CHUNK&lt;/code&gt; message which contains a chunk of file as the payload.  &lt;br /&gt;
When the file completed, the server would then send a &lt;code&gt;MSG_FILE_FINISHED&lt;/code&gt; message with no payload.  &lt;br /&gt;
This means every file download ended with an extra message, &lt;code&gt;MSG_FILE_FINISHED&lt;/code&gt;.  And it has no data...  &lt;br /&gt;
Wasteful!  &lt;br /&gt;
&lt;br /&gt;
So, I extended &lt;code&gt;MSG_FILE_FINISHED&lt;/code&gt; to also contain a payload.  &lt;br /&gt;
Now, the last chunk of file would be &lt;code&gt;MSG_FILE_FINISHED&lt;/code&gt; instead of &lt;code&gt;MSG_FILE_CHUNK&lt;/code&gt;.  &lt;br /&gt;
I was able to remove the pointless extra message of &lt;code&gt;MSG_FILE_FINISHED&lt;/code&gt; with no payload.  &lt;br /&gt;
&lt;br /&gt;
I also uncovered another wasteful message.  &lt;br /&gt;
The code looked like this  &lt;br /&gt;
&lt;pre class="code-pre"&gt;
bytes_read = -1
while bytes_read != 0:
    bytes_read = read_file_into_buffer(buf)
    send_to_client(MSG_FILE_CHUNK, buf)
&lt;/pre&gt;
The intent is to keep sending chunks until nothing left was read from the file, i.e. Everything has been sent.  &lt;br /&gt;
However, &lt;code&gt;while bytes_read != 0&lt;/code&gt; means loop UNTIL &lt;code&gt;bytes_read == 0&lt;/code&gt;, which means we need a pass through the loop that &lt;i&gt;has read&lt;/i&gt; nothing.  &lt;br /&gt;
This causes the server to send a &lt;code&gt;MSG_FILE_CHUNK&lt;/code&gt; with a payload of nothing!&lt;br /&gt;
The server tells the client, "Here's a 0 byte chunk of your download."  &lt;br /&gt;
Wasteful!  &lt;br /&gt;
So, I refactored the loop and the inner logic to not send this useless message.  &lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;In Summary&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Cache data where you can so you do not have to rerequest it&lt;/li&gt;
&lt;li&gt;Look for useless communication. i.e. Data with no payloads&lt;/li&gt;
&lt;li&gt;If you can operate with only local data, do not contact the server&lt;/li&gt;
&lt;/ol&gt;
It's always fun to make these optimizations! Big or Small.  &lt;br /&gt;
Not worrying about them from the beginning allowed me to keep working and deliver value to users.  &lt;br /&gt;
Thinking about everything from the beginning would have been too much, burned me out, and I would have given up.  I was already close to giving up as is, but I'm so glad I didn't!&lt;br /&gt;
&lt;br /&gt;
&lt;a href="https://transmitic.net/"&gt;https://transmitic.net/&lt;/a&gt;&lt;br /&gt;
&lt;a href="https://github.com/transmitic/transmitic/"&gt;https://github.com/transmitic/transmitic/&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;img alt="Transmitic Screenshot" src="https://github.com/transmitic/transmitic/raw/main/screenshot.png" /&gt;&lt;br /&gt;</description><author>Andrew Shay's Blog and Digital Garden</author><pubDate>Sun, 12 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://andrewshay.me/blog/making-transmitic-faster</guid></item><item><title>Essential keyboard shortcuts (Windows/macOS/Linux/UNIX)</title><link>https://jasoneckert.github.io/myblog/shortcuts/</link><description>&lt;p&gt;&lt;img alt="Hands on keyboard" src="hands.jpg#center" title="Hands on keyboard" /&gt;&lt;/p&gt;
&lt;p&gt;Following are some shortcuts that I believe all desktop users should be familiar with. It is not a complete list by any means, and does not include virtual desktop or app pinning shortcuts as I felt those were beyond the scope of common desktop shortcuts. If you feel I omitted a common shortcut, email me (&lt;a href="mailto:jason.eckert@trios.com"&gt;jason.eckert@trios.com&lt;/a&gt;) and I&amp;rsquo;ll add it to the list!&lt;/p&gt;
&lt;h1 id="1-windows-shortcuts"&gt;1. Windows shortcuts&lt;/h1&gt;
&lt;p&gt;The Windows (⊞) key on your keyboard has the Windows logo printed on it. Modern keyboards use the newer Windows logo with four equally-sized squares, while older keyboards use a Windows logo with four slanted or curved squares.&lt;/p&gt;</description><author>Jason Eckert's Website and Blog</author><pubDate>Sun, 12 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jasoneckert.github.io/myblog/shortcuts/</guid></item><item><title>Using a Markov chain to generate readable nonsense with 20 lines of Python</title><link>https://benhoyt.com/writings/markov-chain/</link><description>Describes a simple Markov chain algorithm to generate reasonable-sounding but utterly nonsensical text, and presents some example outputs as well as a Python implementation.</description><author>Ben Hoyt's technical writing</author><pubDate>Sat, 11 Nov 2023 18:30:00 GMT</pubDate><guid isPermaLink="true">https://benhoyt.com/writings/markov-chain/</guid></item><item><title>Roast #32: Coffee Club V5</title><link>https://thoughtfulcoffeenyc.substack.com/p/roast-32-coffee-club-v5</link><description>Orange (Awesome) Blossom, Peaches, and Funk.</description><author>thoughtfulcoffee</author><pubDate>Sat, 11 Nov 2023 17:21:09 GMT</pubDate><guid isPermaLink="true">https://thoughtfulcoffeenyc.substack.com/p/roast-32-coffee-club-v5</guid></item><item><title>A list of the best bug write-ups I've read</title><link>https://jmthornton.net/blog/p/bug-posts</link><description>A modest list of interesting blog posts containing tech mystery stories exploring how a bug or mistake came about, how it was discovered, and how it was fixed.</description><author>Blog by Jade Michael Thornton</author><pubDate>Sat, 11 Nov 2023 17:00:00 GMT</pubDate><guid isPermaLink="true">https://jmthornton.net/blog/p/bug-posts</guid></item><item><title>Javascript Promises</title><link>https://www.danielcorin.com/til/javascript/promises/</link><description>Javascript Promises</description><author>Thought Eddies</author><pubDate>Sat, 11 Nov 2023 13:50:58 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/javascript/promises/</guid></item><item><title>Kubernetes Liveness Check</title><link>https://www.danielcorin.com/til/k8s/liveness-check/</link><description>Kubernetes Liveness Check</description><author>Thought Eddies</author><pubDate>Sat, 11 Nov 2023 13:38:52 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/k8s/liveness-check/</guid></item><item><title>Java agent to unfinalize class</title><link>https://b.agilob.net/programming/java/java-agent-unfinalize-class/</link><description>That one boring Saturday I wanted to learn something more about agents and thought it would be a really cool idea to &amp;ldquo;unfinalize&amp;rdquo; java.lang.String.
I started working on the project, developed simple transformer:
if (name.equals("java/lang/String")) { ClassPool classPool = ClassPool.getDefault(); final CtClass ctClass; try { ctClass = classPool.get(name.replace("/", ".")); } catch (NotFoundException e) { throw new RuntimeException(e); } int modifiers = ctClass.getModifiers(); if (Modifier.isFinal(modifiers)) { System.out.println(name + " modifiers: " + modifiers); ctClass.</description><author>Random Notes by agilob</author><pubDate>Sat, 11 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://b.agilob.net/programming/java/java-agent-unfinalize-class/</guid></item><item><title>Native memory leak in a cloud environment</title><link>https://b.agilob.net/programming/java/native-memory-leak-in-cloud/</link><description>A Java project with published container image that contains intentionally leaky native code to observe symptoms of a memory leak in Java in podman/docker or Kubernetes.
Native code intentionally &amp;ldquo;leaks&amp;rdquo; provided number of megabytes in a loop. The project runs by default with -XX:NativeMemoryTracking=summary enabled.
I wanted to observe how JVM will report native memory, crash and what pod and JVM metrics will look like.
Java doesn&amp;rsquo;t allocate many objects, almost none.</description><author>Random Notes by agilob</author><pubDate>Sat, 11 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://b.agilob.net/programming/java/native-memory-leak-in-cloud/</guid></item><item><title>20 Years of Wasting Time!</title><link>https://skife.org/b4/now-with-more-zola/</link><description>&lt;p&gt;I cannot let 2023 go past without writing &lt;em&gt;anything&lt;/em&gt; given it is 20 years of this blog, but an update to Hugo broke things, and getting it to work was just annoying. So I got frustrated enough making Hugo do what I wanted that I ported over to &lt;a href="https://getzola.org/" rel="external"&gt;Zola&lt;/a&gt; and here we go, I am able to update again!&lt;/p&gt;
&lt;p&gt;Along the way I took a moment to add all the posts from my original blosxom based blog to the &lt;a href="/archive/"&gt;archive&lt;/a&gt;, which makes me happy. Really, I want to post about the home DNS setup I did, but that will have to wait until tomorrow :-) For now, I want to push this and see if the deploy works!&lt;/p&gt;
&lt;p&gt;Happy 2023!&lt;/p&gt;</description><author>Brian's Waste of Time</author><pubDate>Sat, 11 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://skife.org/b4/now-with-more-zola/</guid></item><item><title>Cynical PM Framework, Live at Lean Culture</title><link>https://www.ft.io/blog/cynical-pm-at-lean-culture/</link><description>&lt;p&gt;Thanks to Sean Murphy and Theresa Shafer for inviting me to speak on the &lt;a class="internal-link" href="/blog/cynical-pm/" target="_blank"&gt;Cynical PM Framework&lt;span class="link-preview"&gt;&lt;span class="link-preview-title"&gt;&lt;span class="tt-cta"&gt;&lt;svg height="24px" viewBox="0 0 24 24" width="24px" xmlns="http://www.w3.org/2000/svg"&gt;&lt;path d="M 3 3 L 3 21 L 21 21 L 21 12 L 19 12 L 19 19 L 5 19 L 5 5 L 12 5 L 12 3 L 3 3 z M 14 3 L 14 5 L 17.585938 5 L 8.2929688 14.292969 L 9.7070312 15.707031 L 19 6.4140625 L 19 10 L 21 10 L 21 3 L 14 3 z"&gt;&lt;/svg&gt;&lt;/span&gt;The Cynical PM Framework&lt;span class="tt-sub"&gt;, a business-first approach to product&lt;/span&gt;&lt;/span&gt;&lt;span class="link-preview-excerpt"&gt;As PMs, of course we have a responsibility to our users — but we&amp;#8217;re primarily responsible for advancing the goals of our business. In this post, I posit a simple framework for identifying the different strategic roles your product might play and how to prioritize, position, and measure success once you&amp;#8217;ve identified that role.&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;. Had a blast sharing and discussing it live for the first time. Fantastic insights from Sean &amp;amp; co, and great questions from the whole Lean Culture crew.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://vimeo.com/878730954" rel="nofollow" target="_blank"&gt;Video&lt;/a&gt; and &lt;a href="https://www.skmurphy.com/blog/2023/11/01/frank-tisellano-business-first-product-management/" rel="nofollow" target="_blank"&gt;transcript&lt;/a&gt; inside.&lt;/p&gt;</description><author>Frank Tisellano | ft.io</author><pubDate>Sat, 11 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.ft.io/blog/cynical-pm-at-lean-culture/</guid></item><item><title>Stop deploying web application firewalls</title><link>https://www.macchaffee.com/blog/2023/wafs/</link><description>&lt;p&gt;I wanted to write this because I don't hear enough real people discouraging the use of Web Application Firewalls (WAFs). Probably because the search results for "Web Application Firewall" are all written by WAF vendors. Anyone reading just that could conclude that WAFs are a good idea. I'm here to offer another perspective, after having suffered through using a WAF for two years.&lt;/p&gt;
&lt;p&gt;Web Application Firewalls were created early in the Internet's history, especially popularized by the &lt;a href="https://en.wikipedia.org/wiki/ModSecurity"&gt;ModSecurity project in 2002&lt;/a&gt;. WAFs essentially work by intercepting every single HTTP request (and sometimes responses too) and evaluating several hundred regular expressions over the URI, headers, and body, sometimes aided by machine learning. If the request kinda looks like SQL, shell code, etc., the server may block your request.&lt;/p&gt;
&lt;p&gt;In the infancy of the cybersecurity field, WAFs seemed like a good idea. HTTP requests were tiny, infrequent, and mostly contained mundane form data. But today, WAFs have overstayed their welcome in the security toolbelt. There are better techniques you can use that make even the most advanced WAFs entirely obsolete.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Disclaimer update 2024: In this article, I'm objecting to using WAFs for their original purpose which was to mitigate common exploits like remote code execution, SQL injection, etc. With the rise of DDoS attacks, some cloud WAFs such as AWS WAFv2 or Cloudflare's WAF are now the recommended way of implementing rate limits or fingerprinting and blocking denial of service attacks, which is a use-case I fully support.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="wafs-have-horrible-performance"&gt;WAFs have Horrible Performance&lt;/h2&gt;
&lt;p&gt;Since WAFs run hundreds of regular expressions on every request, you may ask, "isn't that super inefficient?" Yes, very.&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;WAF&lt;/th&gt;&lt;th&gt;No WAF&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Average time taken to upload 9,462 text files&lt;/td&gt;&lt;td&gt;7.36&lt;/td&gt;&lt;td&gt;4.55&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Average requests per second&lt;/td&gt;&lt;td&gt;1285&lt;/td&gt;&lt;td&gt;2079&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Number of requests blocked erroneously&lt;/td&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Peak nginx CPU during trial&lt;/td&gt;&lt;td&gt;73%&lt;/td&gt;&lt;td&gt;8%&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;details&gt;

&lt;em&gt;Specifics about the benchmark&lt;/em&gt;

&lt;hr /&gt;
The easiest way I know to get modsecurity + CoreRuleSet installed is through ingress-nginx, which I've installed in a Kind cluster.
&lt;pre class="language-bash " style="background-color: #2b303b; color: #c0c5ce;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #65737e;"&gt;# https://kind.sigs.k8s.io/docs/user/quick-start/
&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;cat &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span style="color: #b48ead;"&gt;EOF &lt;/span&gt;&lt;span&gt;| &lt;/span&gt;&lt;span style="color: #bf616a;"&gt;kind&lt;/span&gt;&lt;span&gt; create cluster&lt;/span&gt;&lt;span style="color: #bf616a;"&gt; --config&lt;/span&gt;&lt;span&gt;=-
&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;kind: Cluster
&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;apiVersion: kind.x-k8s.io/v1alpha4
&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;nodes:
&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;- role: control-plane
&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;  extraPortMappings:
&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;  - containerPort: 32080
&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;    hostPort: 32080
&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;    protocol: TCP
&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;  - containerPort: 32443
&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;    hostPort: 32443
&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;    protocol: TCP
&lt;/span&gt;&lt;span style="color: #b48ead;"&gt;EOF
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #65737e;"&gt;# https://kubernetes.github.io/ingress-nginx/user-guide/third-party-addons/modsecurity/
&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;helm&lt;/span&gt;&lt;span&gt; upgrade&lt;/span&gt;&lt;span style="color: #bf616a;"&gt; --install&lt;/span&gt;&lt;span&gt; ingress-nginx ingress-nginx \
&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;  --repo&lt;/span&gt;&lt;span&gt; https://kubernetes.github.io/ingress-nginx \
&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;  --namespace&lt;/span&gt;&lt;span&gt; ingress-nginx&lt;/span&gt;&lt;span style="color: #bf616a;"&gt; --create-namespace &lt;/span&gt;&lt;span&gt;\
&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;  --set&lt;/span&gt;&lt;span&gt; controller.service.type=NodePort \
&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;  --set&lt;/span&gt;&lt;span&gt; controller.service.nodePorts.https=32443 \
&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;  --set&lt;/span&gt;&lt;span&gt; controller.service.nodePorts.http=32080 \
&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;  --set&lt;/span&gt;&lt;span&gt; controller.ingressClassResource.default=true \
&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;  --set&lt;/span&gt;&lt;span&gt; controller.allowSnippetAnnotations=true
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the test, I'll be uploading files to MinIO using these values:&lt;/p&gt;
&lt;pre class="language-yaml " style="background-color: #2b303b; color: #c0c5ce;"&gt;&lt;code class="language-yaml"&gt;&lt;span style="color: #bf616a;"&gt;replicas&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #d08770;"&gt;1
&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;mode&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;standalone
&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;resources&lt;/span&gt;&lt;span&gt;:
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #bf616a;"&gt;requests&lt;/span&gt;&lt;span&gt;:
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #bf616a;"&gt;memory&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;512Mi
&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;persistence&lt;/span&gt;&lt;span&gt;:
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #bf616a;"&gt;enabled&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #d08770;"&gt;false
&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;rootUser&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;rootuser
&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;rootPassword&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;rootpass123
&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;buckets&lt;/span&gt;&lt;span&gt;:
&lt;/span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span style="color: #bf616a;"&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;bucket1
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #bf616a;"&gt;policy&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;none
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #bf616a;"&gt;purge&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #d08770;"&gt;false
&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;ingress&lt;/span&gt;&lt;span&gt;:
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #bf616a;"&gt;enabled&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #d08770;"&gt;true
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #bf616a;"&gt;hosts&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;minio-waf.localhost&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #bf616a;"&gt;annotations&lt;/span&gt;&lt;span&gt;:
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #bf616a;"&gt;nginx.ingress.kubernetes.io/enable-modsecurity&lt;/span&gt;&lt;span&gt;: &amp;quot;&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;true&lt;/span&gt;&lt;span&gt;&amp;quot;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #bf616a;"&gt;nginx.ingress.kubernetes.io/enable-owasp-core-rules&lt;/span&gt;&lt;span&gt;: &amp;quot;&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;true&lt;/span&gt;&lt;span&gt;&amp;quot;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #bf616a;"&gt;nginx.ingress.kubernetes.io/modsecurity-snippet&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #b48ead;"&gt;|
&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;      Include /etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf
&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;      SecRuleEngine On
&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;      # Even the core rules are ridiculous, blocking PUT requests, certain content-types, or any body with &amp;quot;options&amp;quot; in it
&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;      SecRuleRemoveById 911100 920420 921110
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class="language-bash " style="background-color: #2b303b; color: #c0c5ce;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #bf616a;"&gt;helm&lt;/span&gt;&lt;span&gt; upgrade&lt;/span&gt;&lt;span style="color: #bf616a;"&gt; --install&lt;/span&gt;&lt;span&gt; minio minio/minio&lt;/span&gt;&lt;span style="color: #bf616a;"&gt; -f&lt;/span&gt;&lt;span&gt; values.yaml&lt;/span&gt;&lt;span style="color: #bf616a;"&gt; -n&lt;/span&gt;&lt;span&gt; minio&lt;/span&gt;&lt;span style="color: #bf616a;"&gt; --create-namespace
&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;helm&lt;/span&gt;&lt;span&gt; upgrade&lt;/span&gt;&lt;span style="color: #bf616a;"&gt; --install&lt;/span&gt;&lt;span&gt; minio-waf minio/minio&lt;/span&gt;&lt;span style="color: #bf616a;"&gt; -f&lt;/span&gt;&lt;span&gt; values-waf.yaml&lt;/span&gt;&lt;span style="color: #bf616a;"&gt; -n&lt;/span&gt;&lt;span&gt; minio-waf&lt;/span&gt;&lt;span style="color: #bf616a;"&gt; --create-namespace
&lt;/span&gt;&lt;span style="color: #65737e;"&gt;# Verify the WAF is working (should get a 403)
&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;curl &lt;/span&gt;&lt;span&gt;'&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;http://minio-waf.localhost:32080/?q=../../etc/passwd&lt;/span&gt;&lt;span&gt;'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We'll be uploading just the "Documentation" folder of the v6.6 Linux Kernel, which contains 9462 files for a total of 65MB.&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #2b303b; color: #c0c5ce;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #bf616a;"&gt;curl -LO&lt;/span&gt;&lt;span&gt; https://github.com/torvalds/linux/archive/refs/tags/v6.6.zip
&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;unzip&lt;/span&gt;&lt;span&gt; v6.6.zip '&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;linux-6.6/Documentation/*&lt;/span&gt;&lt;span&gt;'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Configure the minio client:&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #2b303b; color: #c0c5ce;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #65737e;"&gt;# You may need to add these hosts to /etc/hosts
&lt;/span&gt;&lt;span style="color: #b48ead;"&gt;export &lt;/span&gt;&lt;span style="color: #bf616a;"&gt;MC_HOST_nowaf&lt;/span&gt;&lt;span&gt;='&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;http://rootuser:rootpass123@minio.localhost:32080&lt;/span&gt;&lt;span&gt;'
&lt;/span&gt;&lt;span style="color: #b48ead;"&gt;export &lt;/span&gt;&lt;span style="color: #bf616a;"&gt;MC_HOST_waf&lt;/span&gt;&lt;span&gt;='&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;http://rootuser:rootpass123@minio-waf.localhost:32080&lt;/span&gt;&lt;span&gt;'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run the benchmark (5 times each):&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #2b303b; color: #c0c5ce;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #bf616a;"&gt;time&lt;/span&gt;&lt;span&gt; mc cp&lt;/span&gt;&lt;span style="color: #bf616a;"&gt; -r&lt;/span&gt;&lt;span&gt; linux-6.6/Documentation/ waf/bucket1/
&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;time&lt;/span&gt;&lt;span&gt; mc cp&lt;/span&gt;&lt;span style="color: #bf616a;"&gt; -r&lt;/span&gt;&lt;span&gt; linux-6.6/Documentation/ nowaf/bucket1/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;/details&gt;
&lt;p&gt;In addition to slowing down every request, you also need significant additional RAM for buffering requests. Since not a single byte in the buffer can be flushed to the backend server until the WAF completes its analysis, you need several gigabytes of RAM to store request bodies. Servers like nginx buffer requests by default, but enough large concurrent requests (like pushing a container image) can make a buffering web server run out of RAM. When using a WAF, every server becomes a buffering web server, which is simply incompatible with many types of applications.&lt;/p&gt;
&lt;p&gt;I know computers are fast and hardware is cheap, but we shouldn't be spending that kind of CPU and RAM on WAFs unless they're a really effective security tool. But they aren't, as you'll see next.&lt;/p&gt;
&lt;h2 id="wafs-are-easily-bypassed"&gt;WAFs are Easily Bypassed&lt;/h2&gt;
&lt;p&gt;WAF vendors and attackers are locked in a constant arms race, but it seems &lt;a href="https://github.com/0xInfection/Awesome-WAF#evasion-techniques"&gt;attackers are much better armed&lt;/a&gt;. How could they not be? Many of the attacks that a WAF purports to block involve complex grammars like SQL, shell code, and entire programming languages. They often include comments, character escaping, encoding issues, and more oddities. These oddities mean that attackers always have a significant advantage and can typically bypass any WAF rule if they are clever enough.&lt;/p&gt;
&lt;p&gt;For example, you might think &lt;a href="https://en.wikipedia.org/wiki/Log4Shell"&gt;Log4shell&lt;/a&gt; is pretty easy to catch: just check for &lt;code&gt;${jndi&lt;/code&gt;, right? Unfortunately, Log4J supports nested "&lt;a href="https://logging.apache.org/log4j/2.x/manual/lookups.html"&gt;lookups&lt;/a&gt;", including ones that convert letters to upper/lower case like &lt;code&gt;${lower:J}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;That means an attacker can insert an arbitrary number of nested lookups around each letter and still perform the attack, like this: &lt;code&gt;${${lower:J}ndi:...&lt;/code&gt;. This lead CloudFlare to say &lt;a href="https://blog.cloudflare.com/exploitation-of-cve-2021-44228-before-public-disclosure-and-evolution-of-waf-evasion-patterns/"&gt;"WAF vendors need to be looking at any occurrence of &lt;code&gt;${&lt;/code&gt; and treating it as suspicious"&lt;/a&gt;, which is just another hilarious example of how WAFs can never live up to the expectations placed on them.&lt;/p&gt;
&lt;p&gt;I just discussed the fairly simple grammar that is Log4J Lookups, but you can imagine how many more evasion tactics you could use in a language as complex as SQL or PHP, especially when considering encoding tricks. For an in-depth description of specific WAF bypass techniques, check out &lt;a href="https://habr.com/en/companies/dsec/articles/454592/"&gt;this awesome post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Another way to bypass a WAF involves just padding your attack string to appear &lt;a href="https://docs.aws.amazon.com/waf/latest/developerguide/waf-oversize-request-components.html"&gt;&amp;gt;8KB or so&lt;/a&gt; into the request body. Like I mentioned in the section on performance, request bodies must be buffered into RAM for analysis, so WAFs must choose some cut-off point to avoid spending infinite CPU and RAM on a single request. For some WAFs like AWS's, that cutoff point is around 8KB. So if you just put 8192 innocuous characters before your Log4Shell attack string, you've rendered the WAF worthless.&lt;/p&gt;
&lt;h2 id="wafs-are-an-attack-vector"&gt;WAFs are an Attack Vector&lt;/h2&gt;
&lt;p&gt;In 2019, CapitalOne experienced a breach of 100 million credit applications that was &lt;a href="https://krebsonsecurity.com/2019/08/what-we-can-learn-from-the-capital-one-hack/"&gt;allegedly caused by a WAF misconfiguration&lt;/a&gt;. The attacker allegedly tricked the WAF into sending requests to the EC2 Metadata Service, which handed out a credential that allowed reading sensitive files from S3.&lt;/p&gt;
&lt;p&gt;While this is just one example, it illustrates the curious fact that WAFs actually have a large attack surface.&lt;/p&gt;
&lt;p&gt;Most WAFs are giant, complex codebases that are usually closed-source and written in memory-unsafe languages. Since they're expensive "enterprise" products, companies stuff them full of unnecessary features to make them stand out more than competitors. All of this adds up to make WAFs yet another example of a dangerous "security" tool, &lt;a href="https://www.macchaffee.com/blog/2023/solarwinds-hack-lessons-learned/"&gt;just like SolarWinds&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;No security officer would approve taking such a risky piece of software, putting it directly on the internet, making it parse mountains of untrusted input, and giving it access to all your backend servers, logging infra, SIEM, alerting systems, &lt;a href="https://docs.fastly.com/en/ngwaf/jira"&gt;and even JIRA for some reason&lt;/a&gt; UNLESS it's covered in security buzzwords and costs 5-6 figures per year.&lt;/p&gt;
&lt;p&gt;Somehow, companies that sell security products have gotten a pass on implementing foundational security principles like secure by default, secure by design, attack surface reduction, and the principle of least privilege. Don't let them keep getting away with that.&lt;/p&gt;
&lt;h2 id="wafs-have-a-high-false-positive-rate"&gt;WAFs have a High False Positive Rate&lt;/h2&gt;
&lt;p&gt;Over the last twenty years, open-source WAF rulesets have expanded considerably to detect more-recent types of attack. Apparently all those proprietary WAFs are doing the same. That means there are more and more possible strings that could trigger a WAF to block your request. If you want to write a comment on an article discussing Log4shell, you might be blocked for including the string &lt;code&gt;${jndi&lt;/code&gt; in your comment. So naturally the false positive rate continues to rise with every new rule, and it's already quite high based on my experience maintaining a giant list of ModSecurity rule exceptions.&lt;/p&gt;
&lt;p&gt;So-called "next-generation" WAFs claim to solve this problem by &lt;a href="https://docs.fastly.com/en/ngwaf/about-next-gen-waf"&gt;looking at multiple requests&lt;/a&gt; or by using &lt;a href="https://docs.fastly.com/en/ngwaf/about-the-architecture#about-the-collection-and-analysis-system"&gt;IP reputation systems&lt;/a&gt;. While these can improve false positive rates, they can never truly solve the problem. In some ways, less false positives can increase the impact of particular false positives since neither users nor support teams have a clear procedure for fixing it. CloudFlare's algorithm can randomly decide to block you and &lt;a href="https://www.ctrl.blog/entry/cloudflare-ip-blockade.html"&gt;you will have no recourse&lt;/a&gt;. Imagine that happening to someone less tech-savvy.&lt;/p&gt;
&lt;p&gt;This is the classic problem with using an outdated security tool like a WAF: defenders have to configure the tool absolutely perfectly to be safe and avoid false positives, but attackers just need to find a single weakness. Those are horrible odds. You should use alternatives that don't require perfection from imperfect humans.&lt;/p&gt;
&lt;h2 id="alternatives-to-wafs"&gt;Alternatives to WAFs&lt;/h2&gt;
&lt;p&gt;Since WAFs are resource-hungry, inneffective, unsafe, and noisy, how do I convince an auditor to not make me use one? The technical term would be to use "compensating controls", but that sounds like such a weak term to describe the powerful and simple alternatives to WAFs I'm about to describe:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Isolation:&lt;/strong&gt; Isolation involves ensuring that a breach in one component can not affect the rest of the system, and there are many technologies that provide isolation.
&lt;ul&gt;
&lt;li&gt;Browsers do this by executing all code inside special sandboxed processes that don't have carte blanch access to cookies, saved passwords, other tabs, etc. Imagine how slow the web would be if every piece of JavaScript needed to be analyzed by hundreds of regexes before being executed!&lt;/li&gt;
&lt;li&gt;Microservices are designed with isolation in mind, but you can also do it in a monolith with a variety of &lt;a href="https://github.com/dckc/awesome-ocap#libraries-and-frameworks"&gt;libraries and languages&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Immutability:&lt;/strong&gt; Entire classes of attack can be eliminated by removing a few assumptions, like having a &lt;a href="https://kubernetes.io/docs/tasks/configure-pod-container/security-context/"&gt;readOnlyRootFilesystem&lt;/a&gt;, a &lt;a href="https://thenewstack.io/3-immutable-operating-systems-bottlerocket-flatcar-and-talos-linux/"&gt;package manager that requires rebooting&lt;/a&gt;, or append-only/&lt;a href="https://www.rsync.net/resources/faq.html#9a"&gt;immutable backups&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Static Analysis:&lt;/strong&gt; SQL injection has a miracle cure called "prepared statements". The problem is that devs forget to use them. Static analysis checks in a CI pipeline can all but ensure that zero SQL injection vulnerabilities are in your codebase, at which point there is no need for any SQL injection WAF rules. No, "defense in depth" is not a valid excuse to use a WAF anyway, because it provides no real defense! Like surrounding Fort Knox with an army of guard guinea pigs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Capability-based security:&lt;/strong&gt; Not every API endpoint needs to have unrestricted read/write access to your entire database and file system, but that is the normal way people build APIs today. By using capabilities, you can express exactly that "GET /api/v1/books" only needs read access to the "books" table. Or that "POST /api/v1/imageupload" needs write access to a specific folder, but doesn't need the ability to spawn processes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now I'll admit these ideas are quite broad; you'll need to adapt them to your particular app. WAF vendors offer a one-WAF-fits-all fantasy that I can't match. But these secure-by-design strategies are the way that the security industry needs to be heading. Unfortunately, it's a lot harder for the security industry to profit off of design-based techniques, so don't hold your breath.&lt;/p&gt;</description><author>Mac's Tech Blog</author><pubDate>Sat, 11 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.macchaffee.com/blog/2023/wafs/</guid></item><item><title>Envoy Gateway a new Gateway API for Kubernetes</title><link>https://blog.nobugware.com/post/2023/envoy-gateway-new-gateway-api-kubernetes/</link><description>Envoy Proxy is a well known proxy load balancer offering HTTP, gRPC, TCP, customizable with Lua, Go, WASM &amp;hellip; It&amp;rsquo;s often the core component used to build gateways/proxies for Kubernetes. For example Istio is using it as a proxy load balancer for ingresses but also as a sidecar to create a &amp;ldquo;mesh network&amp;rdquo;.
Envoy Gateway is a more recent project to manage Envoy proxies inside Kubernetes, it is meant to be a common foundation to build on top of it, but it can be used directly as it is.</description><author>Fabrice Aneche</author><pubDate>Sat, 11 Nov 2023 01:01:00 GMT</pubDate><guid isPermaLink="true">https://blog.nobugware.com/post/2023/envoy-gateway-new-gateway-api-kubernetes/</guid></item><item><title>2023-11-10-001</title><link>https://srijan.ch/notes/2023-11-10-001</link><description>My new #IndieWeb enabled website is now live! You can follow my microblog posts or blog articles by entering @srijan.ch@srijan.ch in your Fediverse app search bar or reply and interact with any post using #Webmention or your Mastodon/Fediverse app. Site Features: Indieauth Webmentions Federation with the fediverse (via Bridgy Fed) Structured author, posts, and feeds using microformats Microsub …</description><author>Srijan Choudhary, all posts</author><pubDate>Fri, 10 Nov 2023 15:55:00 GMT</pubDate><guid isPermaLink="true">https://srijan.ch/notes/2023-11-10-001</guid></item><item><title>Suggested readings week 45/2023</title><link>https://www.stefanocosta.org/suggested-readings-week-45-2023/</link><description>&lt;hr /&gt;&lt;h3 id="the-cyber-resilience-act-for-dummies-and-everyone-involved-in-open-source-development"&gt;&lt;a href="https://www.linuxfoundation.org/blog/understanding-the-cyber-resilience-act?hs_amp=true&amp;amp;ref=stefanocosta.org" rel="nofollow noreferrer noopener"&gt;The Cyber Resilience Act for dummies, and Everyone involved in Open Source Development&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;The Cyber Resilience Act was introduced by the European Parliament in September 2022. It will establish cybersecurity requirements for devices and software sold inside the European Union market.&lt;/p&gt;&lt;p&gt;Which means that anybody who places digital products in&lt;/p&gt;</description><author>Food for Thought (by Stefano Costa)</author><pubDate>Fri, 10 Nov 2023 15:46:11 GMT</pubDate><guid isPermaLink="true">https://www.stefanocosta.org/suggested-readings-week-45-2023/</guid></item><item><title>Cheapskate beginners guide to switching from hands-on web development to WordPress</title><link>https://www.lasselaursen.com/post/cheapskate-beginners-guide-to-switching-from-hands-on-web-development-to-wordpress/</link><description>This post is the third in a three-part series about migrating from a self-built PHP-based Laravel backend to a managed WordPress backend, in an attempt to minimize on-going maintenance and custom code. Part one discussed why moving to WordPress felt like a visit to the Ferengi homeworld, and part two detailed what motivated the switch. [&amp;#8230;]</description><author>Lasse Laursen</author><pubDate>Fri, 10 Nov 2023 09:28:46 GMT</pubDate><guid isPermaLink="true">https://www.lasselaursen.com/post/cheapskate-beginners-guide-to-switching-from-hands-on-web-development-to-wordpress/</guid></item><item><title>Monsters, Aliens, and Holes in the Ground (Review)</title><link>https://ztoz.blog/posts/monsters-holes-ground/</link><description>&lt;p&gt;For those interested in the history of tabletop roleplaying game design, &lt;em&gt;Monsters, Aliens, and Holes in Ground&lt;/em&gt; is an excellent, near encyclopedic treatment. The author, Stu Horvath, documents major and minor game systems, how they innovated or were influenced by other game systems, and how the systems expanded with settings and adventures. Horvath covers the 1970s through the 2010s and, reading through the entries, chronicles the major changes in design theory from simulation, to forms of narrative, to today&amp;rsquo;s many experimental systems.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Book Cover" src="./9780262048224.avif" title="Book Cover with alien, barbarian, scholar, and dungeon entrance" /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Stu Horvath and Kyle Patterson. &lt;em&gt;Monsters, Aliens, and Holes in the Ground: A Guide to Tabletop Roleplaying Games from D&amp;amp;D to Mothership&lt;/em&gt;. MIT Press 2023. &lt;a href="https://mitpress.mit.edu/monsters-aliens-and-holes-in-the-ground/"&gt;Publisher Link&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I bought the standard edition and the book is weighty and beautiful. In fact, the book is a bit &lt;em&gt;too&lt;/em&gt; heavy and can be uncomfortable to read in a chair. Over 400 pages long of heavy paper stock, almost all entries are annotated with at least one full-color image of the book being discussed. Each decade is introduced by a thematic two page illustration of the major game design thrusts by Kyle Patterson.&lt;/p&gt;
&lt;p&gt;Organized by the publication date, the text supports a linear read-through as game systems influence later books. Horvath is fairly opinionated about what works and didn&amp;rsquo;t work within a design. This improves the readability, although, as an academic imprint, some of his arguments deserve greater support. (Horvath does pull from game journalists and many interviews with game designers.) I was pleased to finally learn the background of titles like &lt;em&gt;Skyrealms of Jorune&lt;/em&gt;, which was advertised consistently in &lt;em&gt;Dragon Magazine&lt;/em&gt;, but was sufficiently weird to remain obscure.&lt;/p&gt;
&lt;p&gt;Although the book documents an extensive number of systems, it is not exhaustive. The author is open about this fact in the introduction. First, a system may not be included if he has never heard of it. Second, there are many systems (particularly foreign language) that he has not been able to acquire. Third, he excludes some systems if due to their subject matter or his disagreements with the creators. Of these, he only specifies &lt;em&gt;Empire of the Petal Throne&lt;/em&gt; as eliminated. Thus, for some of the rare games in my collection that are not mentioned in the book, I do not know if their absence is intentional or inadvertent.&lt;/p&gt;
&lt;p&gt;Additionally, the main threads of game design are described over many individual game entries. I think the book, and his arguments, would have been improved by a summary tying the major milestones together.&lt;/p&gt;
&lt;p&gt;I recently visited a half-dozen game shops across three states and I was disappointed in the small RPG selection at the stores. If it&amp;rsquo;s not D&amp;amp;D 5e, they are unlikely to have it. In comparison, growing up, every store stocked multiple major systems and many had a deep catalog of used titles. So, I appreciate books like this that show the sheer range of the hobby and how designers have influenced and built upon each other&amp;rsquo;s innovation and experiments. Recommended.&lt;/p&gt;</description><author>ℤ→ℤ</author><pubDate>Fri, 10 Nov 2023 04:26:29 GMT</pubDate><guid isPermaLink="true">https://ztoz.blog/posts/monsters-holes-ground/</guid></item><item><title>(Suggested 📚) Elon Musk</title><link>https://er4hn.info/blog/2023.11.10-elon-musk-bio/</link><description>Then Life Continued - Suggested Reading</description><author>er4hn</author><pubDate>Fri, 10 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://er4hn.info/blog/2023.11.10-elon-musk-bio/</guid></item><item><title>Getting Things Done</title><link>https://vit.baisa.cz/books/getting-things-done/</link><description>a personal productivity system</description><author>Vít Baisa</author><pubDate>Fri, 10 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://vit.baisa.cz/books/getting-things-done/</guid></item><item><title>2023-11-09-001</title><link>https://srijan.ch/notes/2023-11-09-001</link><description>New #Coffee #BlueTokai This new packaging from BlueTokai looks nice. And the coffee tastes amazing. Chocolatey flavour with very little bitterness.</description><author>Srijan Choudhary, all posts</author><pubDate>Thu, 09 Nov 2023 21:25:00 GMT</pubDate><guid isPermaLink="true">https://srijan.ch/notes/2023-11-09-001</guid></item><item><title>ZMK Added the Apple Globe Key</title><link>http://evantravers.com/articles/2023/11/09/zmk-added-the-apple-globe-key/</link><description>&lt;p&gt;I recently noticed that &lt;a href="https://github.com/zmkfirmware/zmk/issues/947"&gt;ZMK added the &amp;quot;globe&amp;quot; key&lt;/a&gt; from the Apple ecosystem to their key codes.&lt;/p&gt;
&lt;p&gt;I threw it on my &lt;a href="https://evantravers.com/articles/2022/04/19/review-corne-ish-zen/"&gt;Corne-ish Zen&lt;/a&gt; and now I have access to &lt;a href="https://thesweetsetup.com/global-keyboard-shortcuts-for-multitasking-in-ipados-15/"&gt;a bunch of useful power user shortcuts&lt;/a&gt; on iPad and iOS. I particularly like starting dictation or going to the previous app.&lt;/p&gt;
&lt;p&gt;I am using it as a &lt;a href="https://zmk.dev/docs/behaviors/sticky-key"&gt;sticky modifier&lt;/a&gt;, so I press it once and then press the next key. That lets me tuck it up in a layer, but you can use it however you want.&lt;/p&gt;
&lt;p&gt;Here's &lt;a href="https://github.com/evantravers/zmk-config/commit/8591c1022bd578681c26dd28a5fa4099eee6ab68"&gt;my commit&lt;/a&gt;.&lt;/p&gt;</description><author>trv.rs</author><pubDate>Thu, 09 Nov 2023 19:09:00 GMT</pubDate><guid isPermaLink="true">http://evantravers.com/articles/2023/11/09/zmk-added-the-apple-globe-key/</guid></item><item><title>Tech Layoffs 2023</title><link>https://mitrapunk.com/tech-layoffs-2023/</link><description>Billionaire property developer Tim Gurner said that "We need to see unemployment rise. We need it to rise 40-50%.</description><author>Mitrapunk: Engineering Game</author><pubDate>Thu, 09 Nov 2023 14:34:06 GMT</pubDate><guid isPermaLink="true">https://mitrapunk.com/tech-layoffs-2023/</guid></item><item><title>Prioritizing Features: A Game That Teaches Real-World Product Management</title><link>https://mitrapunk.com/prioritizing-features-a-game-that-teaches-real-world-product-management/</link><description>Explore 'FeatureFront,' where gaming meets product management. Prioritize features, balance stakeholder demands, and use data to drive decisions, all within a game that simulates real-world tech strategies</description><author>Mitrapunk: Engineering Game</author><pubDate>Thu, 09 Nov 2023 14:27:07 GMT</pubDate><guid isPermaLink="true">https://mitrapunk.com/prioritizing-features-a-game-that-teaches-real-world-product-management/</guid></item><item><title>How to use XmlWriter along with StringWriter to properly serialize a UTF-8 string</title><link>https://nicolaiarocci.com/how-to-use-xmlwriter-along-with-stringwriter-to-properly-serialize-a-utf-8-string/</link><description>&lt;p&gt;Today, I (re)learned how to serialize an XML to a UTF-8 string. Like all the other times I did this, I got backstabbed
by StringWriter, which only supports UTF-16. A simple code snippet like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-gdscript3"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    await using &lt;span style="font-weight: bold;"&gt;var&lt;/span&gt; sw = new StringWriter();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    await using &lt;span style="font-weight: bold;"&gt;var&lt;/span&gt; w = XmlWriter.Create(sw, new() { Async = true });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    ...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    await w.FlushAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="font-weight: bold;"&gt;return&lt;/span&gt; sw.ToString();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Will emit this output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-fallback"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &amp;lt;?xml version="1.0" encoding="utf-16"?&amp;gt;&amp;lt;...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There&amp;rsquo;s nothing inherently wrong with UTF-16, but XML is usually UTF-8, so one must do something about it. StringWriter
exposes an &lt;code&gt;Encoding&lt;/code&gt; property, but it is read-only for unknown reasons. One might think that given that the XmlWriter
allows setting its own &lt;code&gt;Encoding&lt;/code&gt; value, something like this would work:&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Thu, 09 Nov 2023 08:05:25 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/how-to-use-xmlwriter-along-with-stringwriter-to-properly-serialize-a-utf-8-string/</guid></item><item><title>My go-to 'make code go faster' ideas</title><link>https://jodavaho.io/posts/dev-optimization-tricks.html</link><description>&lt;p&gt;I&amp;rsquo;m not an optimization guru by any means. It&amp;rsquo;s never been something I&amp;rsquo;ve been
&lt;em&gt;allowed&lt;/em&gt; to focus on at work, sadly. At some jobs, performance is secondary to
correctness and robustness, and at others, it&amp;rsquo;s secondary to flashy features.&lt;/p&gt;
&lt;p&gt;But, I&amp;rsquo;ve used the following tricks in hotloops&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dimension reduction (esp via convolution)&lt;/li&gt;
&lt;li&gt;Branchless calculation&lt;/li&gt;
&lt;li&gt;SIMD&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;SIMD gets a lot of love, but it&amp;rsquo;s a constant-factor improvement and can be
tough to coax out of the compiler (unless you use a library).&lt;/p&gt;
&lt;p&gt;Branchless calculation is getting less relevant as a &amp;ldquo;trick&amp;rdquo;. A really dumb example is if you have to do this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-c++"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #75715e;"&gt;// Example 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;if&lt;/span&gt; (x&lt;span style="color: #f92672;"&gt;&amp;gt;=&lt;/span&gt;&lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; x&lt;span style="color: #f92672;"&gt;*&lt;/span&gt;y;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;} &lt;span style="color: #66d9ef;"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; x&lt;span style="color: #f92672;"&gt;*&lt;/span&gt;b;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then you can speed it up by doing this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-c++"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #75715e;"&gt;// Example 2:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;int&lt;/span&gt; c &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; (x&lt;span style="color: #f92672;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; x&lt;span style="color: #f92672;"&gt;*&lt;/span&gt;(c&lt;span style="color: #f92672;"&gt;*&lt;/span&gt;y &lt;span style="color: #f92672;"&gt;+&lt;/span&gt; (&lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;&lt;span style="color: #f92672;"&gt;-&lt;/span&gt;c)&lt;span style="color: #f92672;"&gt;*&lt;/span&gt;b); &lt;span style="color: #75715e;"&gt;// either x*y or x*b;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #75715e;"&gt;//better: x*(c*(y-b) + (b));
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And if you can combine this with unrolling and SIMD, then you&amp;rsquo;re really well
off. The second example works just as well if those are &lt;code&gt;Eigen::VectorXd&lt;/code&gt;
length 1000, and now you&amp;rsquo;re really cranking out the speed.&lt;/p&gt;
&lt;p&gt;Something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-c++"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #75715e;"&gt;// Example 3
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;for&lt;/span&gt; ( i .. N )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #66d9ef;"&gt;if&lt;/span&gt; (x[i]&lt;span style="color: #f92672;"&gt;&amp;gt;=&lt;/span&gt;&lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;      out[i] &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; x[i]&lt;span style="color: #f92672;"&gt;*&lt;/span&gt;y[i];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  } &lt;span style="color: #66d9ef;"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;      out[i] x[i]&lt;span style="color: #f92672;"&gt;*&lt;/span&gt;b[i];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Becomes trivial, and looks exactly like &lt;code&gt;//Example 2&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;But even more fundamentally, those usually need to come &lt;em&gt;after&lt;/em&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using the right data structures&lt;/li&gt;
&lt;li&gt;Using the right algorithms&lt;/li&gt;
&lt;li&gt;Reframing the problem for speed, e.g., giving up a little to go a lot faster&lt;/li&gt;
&lt;li&gt;Memoization/caching/pre-calculating&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These can usually get me 80% of the way there. When they all come together,
you&amp;rsquo;re staring at your machine wondering how it is done already.&lt;/p&gt;
&lt;p&gt;I rarely see great returns on multi-threaded / multi-process, unless you&amp;rsquo;re
doing rote calculations on a large dataset in a batch.&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Thu, 09 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/dev-optimization-tricks.html</guid></item><item><title>Monotonic Attention</title><link>https://ben.bolte.cc/posts/2023-11-09-monotonic-attention</link><description>Idea for an implementation of monotonic attention using a probabilistic graphical model.</description><author>Ben Bolte</author><pubDate>Thu, 09 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ben.bolte.cc/posts/2023-11-09-monotonic-attention</guid></item><item><title>Easily Generate Mock Data with PostgreSQL</title><link>https://dylanpaulus.com/posts/2023/easily-generate-mock-data-with-postgresql/</link><description>&lt;p&gt;import { Accordion } from "../../../../components/Accordion";&lt;/p&gt;
&lt;p&gt;When writing articles about PostgreSQL there are times I need to generate large swaths of data to test and demonstrate a topic (for example, these Timescale articles &lt;a href="https://www.timescale.com/blog/how-to-reduce-your-postgresql-database-size/"&gt;here&lt;/a&gt; and &lt;a href="https://www.timescale.com/learn/how-to-reduce-bloat-in-large-postgresql-tables/"&gt;here&lt;/a&gt;).&lt;br /&gt;
It can be a pain to manually insert data or create a CSV and import it into PostgreSQL--which I always have to StackOverflow.&lt;br /&gt;
Lately, I ran into a much simpler solution to generating data: the &lt;code&gt;generate_series&lt;/code&gt; &lt;a href="https://www.postgresql.org/docs/current/functions-srf.html"&gt;function&lt;/a&gt;.&lt;br /&gt;
It's perfectly suited for integers and timestamp data types, streamlining various tasks such as populating test databases or setting up date ranges for reports.&lt;/p&gt;
&lt;h2&gt;Generating Mock Data&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;generate_series&lt;/code&gt; can be thought of as a for-loop or generator to produce a range of numbers or dates.&lt;/p&gt;
&lt;p&gt;To generate a range of numbers, we call &lt;code&gt;generate_series&lt;/code&gt; with a start and end value:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT * FROM generate_series(1, 10);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="The output showing ten rows of integers from one to ten" src="./generate-int-example.png" /&gt;&lt;/p&gt;
&lt;p&gt;We can specify a step value by providing &lt;code&gt;generate_series&lt;/code&gt; as an optional third argument.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT * FROM generate_series(1, 10, 2.5);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="The output with four rows showing 1, 3.5, 6.0, and 8.5 using 2.5 step between one and ten" src="./generate-float-example-step.png" /&gt;&lt;/p&gt;
&lt;h3&gt;Inserting Mock Data&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;generate_series&lt;/code&gt;' power comes to fruition when we need to insert mock data into a table.&lt;br /&gt;
We can easily insert a range of numbers into a table by using &lt;code&gt;generate_series&lt;/code&gt; in a &lt;code&gt;SELECT&lt;/code&gt; statement and then inserting the results into a table.&lt;/p&gt;
&lt;p&gt;For the rest of this article, we'll use &lt;code&gt;users&lt;/code&gt; table that looks like this for our examples:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE TABLE users (
 id serial PRIMARY KEY,
 name text NOT NULL,
 created_on timestamptz
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, if we wanted to insert five users into the table we could run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;INSERT INTO users (name)
SELECT
    'Dylan'
FROM generate_series(1, 5);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="Five rows in the users table all with their name as Dylan" src="./user-dylan-create.png" /&gt;&lt;/p&gt;
&lt;p&gt;This pattern can be used to insert any number of rows into a table.&lt;br /&gt;
If you're feeling brave, try inserting 1,000,000 users into the table by changing &lt;code&gt;generate_series(1, 5)&lt;/code&gt; to &lt;code&gt;generate_series(1, 1000000)&lt;/code&gt;!&lt;/p&gt;
&lt;p&gt;Up to this point we are creating a lot of duplicate data.&lt;br /&gt;
What if we wanted to create a bunch of users with all distinct names?&lt;br /&gt;
We can access the current index of the loop (using &lt;code&gt;as&lt;/code&gt;) to generate a unique name for each user.&lt;/p&gt;
&lt;p&gt;That would look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;INSERT INTO users (name)
SELECT
    'Dylan number ' || i
FROM generate_series(1, 5) as i;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="Five rows in the users table with names: Dylan number 1, Dylan, number 2, and so on until Dylan number 5" src="./user-dylan-with-number.png" /&gt;&lt;/p&gt;
&lt;h2&gt;Creating Time-Series Data&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;generate_series&lt;/code&gt; is incredibly effective at generating time series data.&lt;br /&gt;
This is where I get the most use out of it because it can become extremely tedious to create a lot of data around time ranges.&lt;/p&gt;
&lt;p&gt;Similar to the integer example, generate a range of timestamps by providing a start and end value followed by an optional step or interval.&lt;/p&gt;
&lt;p&gt;For example, to generate a user for every hour in a week, we could run the following SQL:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;INSERT INTO users (name, created_on)
SELECT
    'Dylan',
    time_hour
FROM generate_series(
    TIMESTAMPTZ '2023-11-01',
    TIMESTAMPTZ '2023-11-07',
    INTERVAL '1 hour'
) as time_hour;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="Ten rows in the users table, each with the name Dylan and a created_on column for each hour between 2023-11-01 to 2023-11-07" src="./time-series-hour.png" /&gt;&lt;/p&gt;
&lt;p&gt;This query creates 146 rows with a user named Dylan every hour between November 1st and November 7th, 2023.&lt;/p&gt;
&lt;p&gt;You may be asking yourself, can we use multiple &lt;code&gt;generate_series&lt;/code&gt; to populate multiple fields? Yes!&lt;br /&gt;
But, we need to use joins to combine the data from the two series.&lt;br /&gt;
To quickly create a grid of users and created_on dates we can take the cartesian product of two series, otherwise known as a cross join, by adding another &lt;code&gt;generate_series&lt;/code&gt; to the &lt;code&gt;FROM&lt;/code&gt; clause.&lt;/p&gt;

    A cartesian product is the result of joining two tables
    where the result is the combination of every row in the first table with every row in the second table.
&lt;pre&gt;&lt;code&gt;For example, let's say we have two pants (blue jeans and sweatpants) and three shirts (t-shirt, polo, and sweatshirt).

The cartesian product of our pants and shirts would look like:

| Pants       | Shirts      |
| ----------- | ----------- |
| Blue Jeans  | T-Shirt     |
| Blue Jeans  | Polo        |
| Blue Jeans  | Sweatshirt  |
| Sweatpants  | T-Shirt     |
| Sweatpants  | Polo        |
| Sweatpants  | Sweatshirt  |
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code&gt;INSERT INTO users (name, created_on)
SELECT
    'Dylan number' || i,
    time_hour
FROM
    generate_series(1, 5) as i,
    generate_series(
        TIMESTAMPTZ '2023-11-01',
        TIMESTAMPTZ '2023-11-07',
        INTERVAL '2 days'
    ) as time_hour;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the &lt;code&gt;INSERT&lt;/code&gt; statement above, we'll create four &lt;code&gt;time_hour&lt;/code&gt; timestamps for every two days in the week for every iteration of &lt;code&gt;i&lt;/code&gt; (there are five of them).&lt;br /&gt;
Resulting in &lt;code&gt;4 * 5 = 20&lt;/code&gt; twenty rows created.&lt;br /&gt;
To better grasp what's happening here, it'll be easier to look at the &lt;code&gt;SELECT&lt;/code&gt; output of the users table.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Twenty rows in the users tables showing a cross join between name and the created_on column" src="./multiple-series.png" /&gt;&lt;/p&gt;
&lt;p&gt;Using multiple &lt;code&gt;generate_series&lt;/code&gt; functions combined with a cartesian product can be a powerful combination for creating unique, time-series data.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;PostgreSQL's &lt;code&gt;generate_series&lt;/code&gt; is a game-changer for developers like me, who frequently dive into the deep end of data generation for testing and optimizing queries. It's a powerhouse that can effortlessly create anything from a handful to millions of rows, transforming a potentially arduous task into a few simple lines of SQL.&lt;br /&gt;
So, when you need to mock up data, let &lt;code&gt;generate_series&lt;/code&gt; be your go-to solution.&lt;/p&gt;
&lt;p&gt;Happy data generating!&lt;/p&gt;
&lt;p&gt;Need a realistic dataset to test something more interesting than a flat table? &lt;a href="/posts/postgres-is-a-graph-database"&gt;Postgres: The Graph Database You Didn't Know You Had&lt;/a&gt; uses &lt;code&gt;generate_series&lt;/code&gt; to populate nodes and edges, then walks through recursive CTEs against the same data. For performance work on bigger tables, &lt;a href="/posts/explaining-postgresql-explain"&gt;Explaining PostgreSQL EXPLAIN&lt;/a&gt; covers how to read query plans once the rows pile up.&lt;/p&gt;</description><author>Dylan Paulus' Blog</author><pubDate>Thu, 09 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://dylanpaulus.com/posts/2023/easily-generate-mock-data-with-postgresql/</guid></item><item><title>Binding a C++ Library to 10 Programming Languages 🔟</title><link>https://ashvardanian.com/posts/porting-cpp-library-to-ten-languages/</link><description>Binding USearch to 10 languages reveals the friction in each ecosystem—from Python's simplicity to Java's complexity—and why C++ still rules performance-critical code.</description><author>Ash's Blog</author><pubDate>Thu, 09 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ashvardanian.com/posts/porting-cpp-library-to-ten-languages/</guid></item><item><title>Panic</title><link>https://backendhance.com/blog/2023/panic/</link><description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;p&gt;Die Nervösität zerschnitt den Raum. Ich sah es in den Augen aller Teilnehmer.&lt;/p&gt;
&lt;p&gt;Eigentlich war es nur ein Regeltermin.&lt;/p&gt;
&lt;p&gt;Einmal die Woche kommt die Entwicklung mit den Admins zusammen und bespricht aktuelle Themen.&lt;/p&gt;
&lt;p&gt;Der Termin wird seit Jahren so gemacht.&lt;/p&gt;
&lt;p&gt;Gleiche Zeit, gleiche Menschen, (fast) gleiche Themen.&lt;/p&gt;
&lt;p&gt;Aber etwas war anders.&lt;/p&gt;
&lt;p&gt;Es nahm zum ersten Mal ein neuer Kollege Teil. Ein Externer.&lt;/p&gt;
&lt;p&gt;Wir haben ihn zu uns geholt, um an &amp;ldquo;Devops&amp;rdquo; zu arbeiten. Also, hauptsächlich Automatisierung. CI/CD, Provisionierung, Tooling, Compilezeit-Optimierungen. Alles was die Developer Experience verbessert.&lt;/p&gt;</description><author>Backendhance</author><pubDate>Thu, 09 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/panic/</guid></item><item><title>GopherConSyd 2023 - All slides, notes and links</title><link>https://boyter.org/posts/gopherconsyd-2023/</link><description>&lt;p&gt;Welcome. If you are reading this you either follow me on RSS, watch my blog or were directed here at the start of my GopherConSyd 2023 talk about &amp;ldquo;Bloom Filters: Building a Cutting Edge Go Search Engine to Explore the World&amp;rsquo;s Source Code&amp;rdquo;. Whatever the case may be, welcome!&lt;/p&gt;
&lt;p&gt;For the slides visit &lt;a href="https://boyter.org/static/gophercon-syd-presentation-2023/"&gt;https://boyter.org/static/gophercon-syd-presentation-2023/&lt;/a&gt; then just use the space bar to advance, the arrow keys to move around or the ESC key to see everything and then enter to select a slide. Press S to see the speaker notes, which are more or less the script I will be sticking to.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Thu, 09 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/gopherconsyd-2023/</guid></item><item><title>Pain is information</title><link>https://stephango.com/pain</link><description>&lt;p&gt;As a child, you touched something hot, and it burned you. That pain gave you a piece of information: be careful touching hot things.&lt;/p&gt;

&lt;p&gt;When you sign up to run a marathon, you are signing up for pain. But whether or not you keep running is up to you. It’s been said that “pain is inevitable but suffering is optional”. You can choose pain without choosing suffering.&lt;/p&gt;

&lt;p&gt;Pain is information, and information is painful. Rewiring your brain is not a frictionless process. Some knowledge can only be discovered the hard way.&lt;/p&gt;

&lt;p&gt;The sooner you can convert pain into knowledge, the sooner you can experience the next &lt;em&gt;useful&lt;/em&gt; pain. Don’t put yourself through the same pain too many times. To access new information you must experience &lt;em&gt;new pains&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;They say “knowledge is power”. If pain is information, then pain can be converted to power. To do so, you must learn to control your suffering, accept that &lt;a class="internal-link" href="/scars"&gt;scars are beautiful&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you felt pain, ask what information it gave you. If that information was useful, seek the next pain. You are learning.&lt;/p&gt;</description><author>Steph Ango</author><pubDate>Thu, 09 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://stephango.com/pain</guid></item><item><title>A virtual machine approach to fuzzing interfaces</title><link>https://makedist.com/posts/2023/11/09/a-virtual-machine-approach-to-fuzzing-interfaces/</link><description>Clang&amp;rsquo;s libfuzzer can design adverserial programs.</description><author>Noah Watkins</author><pubDate>Thu, 09 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://makedist.com/posts/2023/11/09/a-virtual-machine-approach-to-fuzzing-interfaces/</guid></item><item><title>Love as a place</title><link>https://bowendwelle.substack.com/p/love-as-a-place</link><description>a clutch of roses dropped in a Capp Street crosswalk</description><author>An Ordinary Disaster</author><pubDate>Wed, 08 Nov 2023 23:49:54 GMT</pubDate><guid isPermaLink="true">https://bowendwelle.substack.com/p/love-as-a-place</guid></item><item><title>A blog post is a very long and complex search query to find fascinating people and make them route interesting stuff to your inbox</title><link>http://evantravers.com/articles/2023/11/08/a-blog-post-is-a-very-long-and-complex-search-query-to-find-fascinating-people-and-make-them-route-interesting-stuff-to-your-inbox/</link><description>&lt;p&gt;The title says it all.&lt;/p&gt;
&lt;p&gt;The essay says more… and rings true with my experience.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I had written an essay about Ivan Illich and systems thinking, a topic I had never found anyone else intrigued by, and which magazines thought below a rejection letter—and the internet had suddenly reshaped itself so that my keyboard hooked up to the screens of a bunch of people who wanted to talk about these topics, and a little later, their keyboards hooked up to mine.&lt;/p&gt;
&lt;p&gt;I had written for 15 years, but never before had this happened. I had conjured a minor conference! And I hadn’t even known that you could do that.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Write and publish. The more specific and true to you the better. If you have a same path of conversation twice with different friends… that would probably be a good blog post.&lt;/p&gt;&lt;p&gt;&lt;a class="u-in-reply-to  is-reply" href="https://www.henrikkarlsson.xyz/p/search-query"&gt;A blog post is a very long and complex search query to find fascinating people and make them route interesting stuff to your inbox - www.henrikkarlsson.xyz&lt;/a&gt;&lt;/p&gt;</description><author>trv.rs</author><pubDate>Wed, 08 Nov 2023 20:23:00 GMT</pubDate><guid isPermaLink="true">http://evantravers.com/articles/2023/11/08/a-blog-post-is-a-very-long-and-complex-search-query-to-find-fascinating-people-and-make-them-route-interesting-stuff-to-your-inbox/</guid></item><item><title>Where To Go For Answers To Flutter Questions</title><link>https://fredgrott.medium.com/where-to-go-for-answers-to-flutter-questions-51da85b31a22?source=rss-2cddef1829fb------2</link><description>&lt;div class="medium-feed-item"&gt;&lt;p class="medium-feed-image"&gt;&lt;a href="https://fredgrott.medium.com/where-to-go-for-answers-to-flutter-questions-51da85b31a22?source=rss-2cddef1829fb------2"&gt;&lt;img src="https://cdn-images-1.medium.com/max/1440/1*XAqT0UFksq221CYF1RBrcw.png" width="1440" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p class="medium-feed-snippet"&gt;AI generated stuff has flowed into several QA forums including stackoverflow.&lt;/p&gt;&lt;p class="medium-feed-link"&gt;&lt;a href="https://fredgrott.medium.com/where-to-go-for-answers-to-flutter-questions-51da85b31a22?source=rss-2cddef1829fb------2"&gt;Continue reading on Medium »&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;</description><author>Stories by Fred Grott on Medium</author><pubDate>Wed, 08 Nov 2023 14:35:13 GMT</pubDate><guid isPermaLink="true">https://fredgrott.medium.com/where-to-go-for-answers-to-flutter-questions-51da85b31a22?source=rss-2cddef1829fb------2</guid></item><item><title>🇩🇪 My Solo Journey Through Germany</title><link>https://james-carr.org/posts/2023-solo-trip-germany/</link><description>I recently returned from a week-long solo adventure in Germany, a trip that came about quite unexpectedly. Earlier in 2023, in my eagerness to reengage with speaking at conferences, I submitted a series of talk proposals to various events and one of my submissions was accepted at RabbitMQ Summit. However, it was only after this exciting acceptance that I realized the conference was in Berlin, not in the United States.</description><author>James Carr</author><pubDate>Wed, 08 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://james-carr.org/posts/2023-solo-trip-germany/</guid></item><item><title>The Hitchhiker's Guide to the Galaxy</title><link>https://vit.baisa.cz/books/the-hitchhikers-guide-to-the-galaxy/</link><description>a classic sci-fi comedy novel dealing with deep philosophical questions</description><author>Vít Baisa</author><pubDate>Wed, 08 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://vit.baisa.cz/books/the-hitchhikers-guide-to-the-galaxy/</guid></item><item><title>You shouldn’t use Lambda layers</title><link>https://aaronstuyvenberg.com/posts/why-you-should-not-use-lambda-layers</link><description>AWS Lambda layers can help in certain, narrow use cases. But they don't help reduce overall function size, they don't improve cold starts, and they leave you vulnerable to a particularly nasty bug.</description><author>AJ Stuyvenberg</author><pubDate>Wed, 08 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://aaronstuyvenberg.com/posts/why-you-should-not-use-lambda-layers</guid></item><item><title>Note 122</title><link>https://qubyte.codes/notes/1699386639456</link><description>&lt;p&gt;Hello #IndieWeb enthusiasts. I’m holding an unofficial Homebrew Website Club at the Lord Nelson Inn near Brighton station tomorrow evening. I’ll be there around 17:30, wrapping up around 20:00. #ffconf folk most welcome!&lt;/p&gt;
&lt;p&gt;I’ll post more information when I’ve found a spot, but if you look for a balding dude with a beard and a dubious waxed moustache the odds are good that you’ll find me.&lt;/p&gt;</description><author>Qubyte Codes</author><pubDate>Tue, 07 Nov 2023 21:50:39 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/notes/1699386639456</guid></item><item><title>Snyk Language Server in Emacs</title><link>https://jmthornton.net/blog/p/snyk-ls-emacs</link><description>Setting up the Snyk Language Server for use in Emacs by defining a custom server connection in LSP Mode</description><author>Blog by Jade Michael Thornton</author><pubDate>Tue, 07 Nov 2023 17:00:00 GMT</pubDate><guid isPermaLink="true">https://jmthornton.net/blog/p/snyk-ls-emacs</guid></item><item><title>ChatGPT Wrote my Shell Prompt</title><link>https://zacs.site/blog/chatgpt-shell-prompt.html</link><description>&lt;p&gt;I have, for years now, wanted a cool shell prompt&amp;#160;&amp;#8212;&amp;#160;something worth showing off on &lt;a href="https://www.reddit.com/r/linux/"&gt;r/linux&lt;/a&gt; or even &lt;a href="https://www.reddit.com/r/unixporn/"&gt;r/unixporn&lt;/a&gt;. Not enough to dig into the weeds and figure out how to write one myself, but enough that it came up every once in a while. Then, the other day, I had a great idea: why not just let ChatGPT make it for me?&lt;/p&gt;
                &lt;p&gt;&lt;a href="https://zacs.site/blog/chatgpt-shell-prompt.html"&gt;Permalink.&lt;/a&gt;&lt;/p&gt;</description><author>Zac Szewczyk</author><pubDate>Tue, 07 Nov 2023 05:04:30 GMT</pubDate><guid isPermaLink="true">https://zacs.site/blog/chatgpt-shell-prompt.html</guid></item><item><title>Anchor Tags</title><link>https://www.marginalia.nu/log/93_atags/</link><description>&lt;p&gt;I&amp;rsquo;ve been working on getting anchor tag keywords into the search engine,
basically using link texts to complement the keywords on a webpage.&lt;/p&gt;
&lt;p&gt;The problem I&amp;rsquo;m attempting to address is that many websites don&amp;rsquo;t really describe
themselves particularly well. As Steve Ballmer&amp;rsquo;s stage performance once illustrated,
merely repeating a word doesn&amp;rsquo;t on its own make what you&amp;rsquo;re saying relevant to the term.&lt;/p&gt;
&lt;p&gt;Another good example of how it falls short is &lt;a href="https://www.chiark.greenend.org.uk/~sgtatham/putty/"&gt;PuTTY&amp;rsquo;s website&lt;/a&gt;,
which will be used as a pilot case to improve.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Tue, 07 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/93_atags/</guid></item><item><title>Openai Assistant Example</title><link>/2023/11/07/OpenAI-Assistant-Example.html</link><description>Video</description><author>rrich.io</author><pubDate>Tue, 07 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">/2023/11/07/OpenAI-Assistant-Example.html</guid></item><item><title>Flutter Docs Missing DevOPS</title><link>https://fredgrott.medium.com/flutter-docs-missing-devops-8db3ff39694e?source=rss-2cddef1829fb------2</link><description>&lt;div class="medium-feed-item"&gt;&lt;p class="medium-feed-image"&gt;&lt;a href="https://fredgrott.medium.com/flutter-docs-missing-devops-8db3ff39694e?source=rss-2cddef1829fb------2"&gt;&lt;img src="https://cdn-images-1.medium.com/max/1080/1*s5zzVbsVZ46ukQjsC020VA.png" width="1080" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p class="medium-feed-snippet"&gt;Google GDE&amp;#x2019;s are not telling you this but their team builds flutter apps on fast non Apple hardware but then the final release builds&amp;#x2026;&lt;/p&gt;&lt;p class="medium-feed-link"&gt;&lt;a href="https://fredgrott.medium.com/flutter-docs-missing-devops-8db3ff39694e?source=rss-2cddef1829fb------2"&gt;Continue reading on Medium »&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;</description><author>Stories by Fred Grott on Medium</author><pubDate>Mon, 06 Nov 2023 18:28:54 GMT</pubDate><guid isPermaLink="true">https://fredgrott.medium.com/flutter-docs-missing-devops-8db3ff39694e?source=rss-2cddef1829fb------2</guid></item><item><title>Fix iPhone not sending/receiving text and photos with Android devices</title><link>https://michaelcarrano.com/blog/fix-iphone-messages-with-android</link><description>&lt;p&gt;I recently helped a family member fix an issue they were having with their iPhone 15 Pro, they were unable to send or receive any photos (SMS/MMS) if they were texting a person that has an Android device. If they disabled iMessage, then the photos were being sent as HTML code to the Android device.&lt;/p&gt;

&lt;p&gt;I was one such person affected, I would text them photos, and on my end, it shows up as being sent. They never received it and I was left wondering why they weren’t responding to me. If they were trying to send a photo, then iMessage just showed a “not sent” error but no details as to why.&lt;/p&gt;

&lt;p&gt;This family member wasted a bunch of time talking to Apple Customer Support who pointed their fingers at Verizon, saying it is a carrier issue. They would then give Verizon a call and explain things over again, only for Verizon to say it is an Apple bug and to wait for a new iOS update. New iOS updates would come and the issue would still exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spoiler alert, it ended up being a Verizon issue.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After Googling various phrases related to the issue and following the usual troubleshooting steps:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Disable iMessage and Facetime&lt;/strong&gt;: This didn’t solve the issue. This is when photos would arrive as HTML to Android devices.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Reset network settings&lt;/strong&gt;: This didn’t solve the issue.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Double check iMessage and Facetime contact information, ensuring their phone number was selected&lt;/strong&gt;: This didn’t solve the issue.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Remove old devices (phones and Macbooks) from their Apple ID&lt;/strong&gt;: This didn’t solve the issue.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since this family member uses Verizon’s service, what finally fixed the issue of sending and receiving photos with Android devices was to install the &lt;a href="https://apps.apple.com/us/app/my-verizon/id416023011"&gt;My Verizon App&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Install the &lt;a href="https://apps.apple.com/us/app/my-verizon/id416023011"&gt;My Verizon App&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Log into your account (might need to log in with the owner account)&lt;/li&gt;
  &lt;li&gt;Scroll down to manage the iPhone that is having issues and select it&lt;/li&gt;
  &lt;li&gt;Scroll down to find &lt;a href="https://www.verizon.com/support/knowledge-base-211865/"&gt;block specific services&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Make sure &lt;strong&gt;Block Picture &amp;amp; Video Messaging&lt;/strong&gt; is not enabled, as that won’t allow sending/receiving the photos/videos from Android devices.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For an unknown reason, the Block Picture &amp;amp; Video Messaging was enabled on this family member’s device. So once we disabled it, forced quit the app, and then double checked it was still disabled, everything started working as expected.&lt;/p&gt;

&lt;p&gt;If you are having this issue with AT&amp;amp;T or T-Mobile as your carrier, I would check to see if their apps have a similar functionality.&lt;/p&gt;</description><author>Mobile Software Engineer in NYC.</author><pubDate>Mon, 06 Nov 2023 10:06:33 GMT</pubDate><guid isPermaLink="true">https://michaelcarrano.com/blog/fix-iphone-messages-with-android</guid></item><item><title>Maybe don't call someone an asshole?</title><link>https://greaterdanorequalto.com/maybe-dont-call-someone-an-asshole/</link><description>Arguments that feel good to make generally aren't very persuasive.</description><author>Greater Dan Or Equal To</author><pubDate>Mon, 06 Nov 2023 09:05:36 GMT</pubDate><guid isPermaLink="true">https://greaterdanorequalto.com/maybe-dont-call-someone-an-asshole/</guid></item><item><title>Kids Treasure Hunt</title><link>https://backendhance.com/blog/2023/kids-treasure-hunt/</link><description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Für eine Schatzsuche für einen 5 Jährigen möchte ich einen Text schreiben. Der Text soll von einem Piraten geschrieben sein, der ein Rätsel beschreibt wo der Schatz des Piraten versteckt ist. Er soll an einem Ort versteckt sein wo gruselige Bäume sind. Kannst du mir den Text schreiben und ihn reimen lassen?&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Mit diesem Prompt habe ich ChatGPT gefüttert. Ich war gerade &lt;a href="https://www.linkedin.com/posts/marcus-held_it-was-an-honor-for-me-to-open-the-rabbitmq-activity-7121068312075067392-nMK9" rel="noopener noreferrer" target="_blank"&gt;aus Berlin von der RabbitMQ Summit zurück&lt;i class="bx bx-link-external"&gt;&lt;/i&gt;&lt;/a&gt;
.&lt;/p&gt;</description><author>Backendhance</author><pubDate>Mon, 06 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/kids-treasure-hunt/</guid></item><item><title>Accessibility is a requirement, not a feature</title><link>https://ntietz.com/blog/accessibility-is-a-requirement-not-a-feature/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;Stop me if you've heard this one before: "We're putting accessibility (features) on the roadmap."
Or this one: "We don't need to make it accessible since we don't have any blind users&lt;sup class="footnote-reference"&gt;
  &lt;a href="https://ntietz.com/blog/accessibility-is-a-requirement-not-a-feature/#fn-1" id="fn-ref-1"&gt;1&lt;/a&gt;
&lt;/sup&gt;
."&lt;/p&gt;
&lt;p&gt;It belies an attitude that's all too common in the software industry:
That accessibility is something you can build once and be done with.
That it's an extra feature, not something core to a product.
That it's optional, a business decision, to make your product accessible.&lt;/p&gt;
&lt;p&gt;Just as with security, this is a misunderstanding of the nature of accessibility.
Security is something you have to always think about, and always work on; you are never "done".
And the same with accessibility.&lt;/p&gt;
&lt;h1 id="learning-firsthand-about-accessibility"&gt;Learning firsthand about accessibility&lt;/h1&gt;
&lt;p&gt;For most of my life, I had not needed accessibility tech&lt;sup class="footnote-reference"&gt;
  &lt;a href="https://ntietz.com/blog/accessibility-is-a-requirement-not-a-feature/#fn-2" id="fn-ref-2"&gt;2&lt;/a&gt;
&lt;/sup&gt;
.
I could use products on the "happy path" of good mobility, vision, and hearing.
Almost everything was accessible to me, because I had the abilities that designers expected of users.&lt;/p&gt;
&lt;p&gt;Then my arms and hands failed me.
In the summer and fall of 2022, I developed nerve pain in my arms.
If I typed for more than a couple of sentences, I would get this strong pain in one of my arms and hands, and I always had weird nerve sensations.
I didn't recognize what it was at first, because it first felt like I'd just scraped the skin.
The pain developed fairly quickly: my arm felt funny on a walk with the kids, then in the evening I was typing and it hurt, and by the morning I couldn't drive because the steering wheel vibrations were intensely painful.&lt;/p&gt;
&lt;p&gt;I'm a software engineer, so typing has always been core to my ability to do my job.
I had previously said that my hands were my most valuable body part, probably after my brain.
So when I was unable to type, I was pretty scared.
What would happen to me, and my family, if I couldn't type and couldn't do my job?&lt;/p&gt;
&lt;p&gt;Enter: accessibility.
I invested in learning to use &lt;a href="https://talonvoice.com/"&gt;Talon&lt;/a&gt; so that I could write code again, write Slack messages again.
It was slow, it interfered with my thinking since it wasn't natural, but it was so &lt;em&gt;empowering&lt;/em&gt; to be able to produce something without physical pain.&lt;/p&gt;
&lt;p&gt;For a little while it was just the one arm, but when the second one developed pain as well I could no longer use my mouse without pain.
Now I would have to use the keyboard to navigate everything.
I'm about to find out firsthand just how inconsistent accessibility is.
Some software worked very well for me, including terminals and our own product.
Other software, like a well-known issue tracker, was all but unusable without a mouse and required very different workflows to work around it.
Most software was somewhere in the middle, with a lot working but some commonly used features just failing; I still don't know how to go back and edit a specific message in Slack without a keyboard, only the most recent one.&lt;/p&gt;
&lt;h1 id="accessibility-affects-everyone"&gt;Accessibility affects everyone&lt;/h1&gt;
&lt;p&gt;I'm not alone in my brush with accessibility.
If you've ever broken an arm or a leg, you know how hard it can be to interact with the world with limited mobility.
If you've lost your glasses and stumbled around at dawn on the running trail&lt;sup class="footnote-reference"&gt;
  &lt;a href="https://ntietz.com/blog/accessibility-is-a-requirement-not-a-feature/#fn-3" id="fn-ref-3"&gt;3&lt;/a&gt;
&lt;/sup&gt;
, you know vision impairment can make everything harder to use.&lt;/p&gt;
&lt;p&gt;There are plenty of statistics out there on how many people are disabled, and how many will be temporarily disabled.
That's there, a search away.
Right here, though, is an argument from our humanity.&lt;/p&gt;
&lt;p&gt;We all go through life with a tenuous relationship with our abilities.
As my loss (and regaining) of typing ability showed me, we can lose some of our abilities on a moment's notice, and this loss can be temporary.
Each of us may become disabled at any point in the future, without warning.
Sometimes it's temporary; sometimes it's permanent.&lt;/p&gt;
&lt;h1 id="accessibility-isn-t-optional"&gt;Accessibility isn't optional&lt;/h1&gt;
&lt;p&gt;"People with disabilities" isn't a demographic you can choose to ignore.
In many ways, it's not a demographic&lt;sup class="footnote-reference"&gt;
  &lt;a href="https://ntietz.com/blog/accessibility-is-a-requirement-not-a-feature/#fn-4" id="fn-ref-4"&gt;4&lt;/a&gt;
&lt;/sup&gt;
, but a shifting subset of the population.
But if you do choose to ignore people with disabilities, you're hurting everyone.&lt;/p&gt;
&lt;p&gt;If you think you don't have users with disabilities, you... might be right, in the worst way.
Your software might be &lt;em&gt;so inaccessible&lt;/em&gt; that users with any sort of disability cannot use it.
That was nearly my experience with an issue tracker; it was almost impossible to use, I had people move issues for me sometimes.
But that doesn't mean that no one with a disability tries to, wants to, or should be able to use your software.&lt;/p&gt;
&lt;p&gt;There's a famous story &lt;a href="https://en.wikipedia.org/wiki/Survivorship_bias#Military"&gt;about planes in World War II&lt;/a&gt; where we were armoring the wrong parts of them.
We were seeing only the ones that survived, so we assumed that where there weren't bullet holes, we didn't need to add armor.
But that's &lt;em&gt;exactly&lt;/em&gt; where we needed more armor, because a hit in that area would down the plane entirely.&lt;/p&gt;
&lt;p&gt;This is the same situation in software.
If you're looking at the population as a whole and a particular segment of it is not represented in your userbase, it might be your fault.
It means that something about your software isn't working for those people.
If you have no users with disabilities, then... they probably &lt;em&gt;can't&lt;/em&gt; use your software if they wanted to.&lt;/p&gt;
&lt;p&gt;It's kind of silly to say "we have no blind users, so we don't need to make the software screen-reader friendly."
If you never do the work, they'll never be able to use it.&lt;/p&gt;
&lt;p&gt;On top of all this, accessibility is required by law for a great deal of software.
I'm not a lawyer, so that's just about where I'll leave it, but be aware that in the US and other countries, you may be open to lawsuits if you don't make your software reasonable accessible.&lt;/p&gt;
&lt;h1 id="a-requirement-not-a-feature"&gt;A requirement, not a feature&lt;/h1&gt;
&lt;p&gt;Accessibility is something that may affect each of us.
We have brushes with it throughout our lives.
And it's something that isn't optional.&lt;/p&gt;
&lt;p&gt;It's not something we can put on our roadmaps once and be done with.
It demands continued effort throughout the entire process of software development, just as with security and performance.
Accessibility is something you work on throughout the life cycle of your product.&lt;/p&gt;
&lt;p&gt;Don't get me wrong, accessibility can have &lt;em&gt;features&lt;/em&gt;.
There are sometimes features you can add to make software much more accessible in particular ways.
Just, accessibility as a whole isn't a &lt;em&gt;feature&lt;/em&gt;, but a &lt;em&gt;requirement&lt;/em&gt; for development.
It's part of each feature you work on.&lt;/p&gt;
&lt;p&gt;Making a feature accessible is just one of many aspects of the work to complete a feature.
You have to make that feature secure, and performant, and accessble.
It's not something you can delay or choose not to do; it's something you &lt;em&gt;must&lt;/em&gt; do as part of routine development.&lt;/p&gt;
&lt;p&gt;If you want to do the right thing, make sure you add accessibility requirements as part of the completion criteria for things you work on.&lt;/p&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 06 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/accessibility-is-a-requirement-not-a-feature/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Testing against every Next.js canary release</title><link>https://francoisbest.com/posts/2023/testing-against-every-nextjs-canary-release?utm_source=rss</link><description>How to run a GitHub Actions workflow when a new pre-release version of Next.js is published.</description><author>Articles by François Best</author><pubDate>Mon, 06 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://francoisbest.com/posts/2023/testing-against-every-nextjs-canary-release?utm_source=rss</guid></item><item><title>Catitecture</title><link>https://lagomor.ph/indieweb/20231105/1245-like/</link><description/><author>Home on Lagomorph</author><pubDate>Sun, 05 Nov 2023 21:46:00 GMT</pubDate><guid isPermaLink="true">https://lagomor.ph/indieweb/20231105/1245-like/</guid></item><item><title>Contra Posturing</title><link>https://bastian.rieck.me/blog/2023/posturing/</link><description>&lt;p&gt;With recent events in the Middle East escalating and spiralling further,
I need to voice some thoughts that I held back for some time now. It all
started with Russia&amp;rsquo;s invasion of Ukraine. Back then, I had already seen
my social media feeds light up in frantic activity. Having endured a lot
of self-appointed virology experts during the pandemic,&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; I now had to
endure armchair warfare experts and historians. Given how wildly off the
most of their statements turned out to be, I have the feeling that their
claim to fame was probably copious experience with &lt;a href="https://en.wikipedia.org/wiki/Civilization_(series)"&gt;Civ&lt;/a&gt; and
&lt;a href="https://en.wikipedia.org/wiki/Risk_(game)"&gt;Risk&lt;/a&gt;&amp;hellip;&lt;/p&gt;
&lt;p&gt;Nevertheless, the battle lines were drawn viciously and with a lot of
vitriol. Profile pictures were changed, tag lines were added, and the
Twitter feeds were full of statements of solidarity. So far, so good.
Amidst all that noise, there were few people willing to &lt;em&gt;listen&lt;/em&gt;, and
consider what type of help would be really &lt;em&gt;helpful&lt;/em&gt;. This is to some
extent just par for the course, and I am guilty of that myself. It is
so much easier to &amp;lsquo;stand in solidarity with someone&amp;rsquo; if I do not have
to change my views or my behaviour! I feel reminded of a quote by the
young &lt;a href="https://en.wikipedia.org/wiki/Augustine_of_Hippo"&gt;Augustine of Hippo&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;O Lord, make me chaste, but not yet!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In that sense, social media has unfortunately driven many of us to be
posturing and signalling instead of actually doing something. For the
events in Ukraine, I am somewhat personally affected,&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt; and I found
myself going for a social media hiatus because I was well fed up with
people taking cheap discussion pot-shots and spitting at their screen
while somewhere else, real human beings were dying.&lt;/p&gt;
&lt;p&gt;(And the worst thing is, &lt;strong&gt;I am not one iota better&lt;/strong&gt;; for every day,
people are killed in conflicts all over the world, but I ignore most,
if not all, of these conflicts.)&lt;/p&gt;
&lt;p&gt;Now, with the vicious &lt;a href="https://en.wikipedia.org/wiki/2023_Hamas_attack_on_Israel"&gt;Hamas terror attack on Israel&lt;/a&gt;,
a new ugly set of discussions is cropping up. There is one difference
this time: the battle lines are way more murky; at the same time, the
convictions of those that have no skin in the game run even deeper. I
see many appalling, dehumanising statements by folks that are neither
affected in any sense nor&amp;mdash;or so it seems to me&amp;mdash;do they understand
this conflict as well as they think they do. I have no such delusions
and &lt;strong&gt;know for a fact that I lack understanding of the context&lt;/strong&gt; so I
do not feel compelled to duke it out with others that are as ignorant
as I am.&lt;/p&gt;
&lt;p&gt;Now, the prevailing mores of social media being what they are, a lack
of understanding never prevented a hot take, and outrage drives a lot
of the engagements. Maybe, just maybe, now would be a good time for a
little bit of &lt;strong&gt;listening&lt;/strong&gt; and &lt;strong&gt;reflecting on what is said.&lt;/strong&gt; These
complex topics cannot be addressed by a good-or-evil narrative alone.
That&amp;rsquo;s too much like &amp;lsquo;if you are not an apple, you are a banana!&amp;rsquo;&lt;sup id="fnref:3"&gt;&lt;a class="footnote-ref" href="#fn:3"&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;I believe that a genuine contribution to any discussion requires some
understanding that transcends feelings of outrage. If you are not one
of the affected groups, listen first and &lt;strong&gt;think&lt;/strong&gt;, lest you be taken
advantage of. None of us want to end up like the vaccine-hating shill
who peddles &lt;a href="https://www.fda.gov/consumers/consumer-updates/ivermectin-and-covid-19"&gt;Ivermectin&lt;/a&gt;
to people scared of a pandemic.&lt;sup id="fnref:4"&gt;&lt;a class="footnote-ref" href="#fn:4"&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;I hope peace will soon be restored, but it does not look like it. One
thing is sure, though: For &lt;em&gt;any&lt;/em&gt; conflict to end peacefully, there is
no need for armchair experts and keyboard warriors. I think it is all
right to leave certain things to the pros. Heeding the ancient wisdom
&lt;a href="https://en.wikipedia.org/wiki/Ne_supra_crepidam"&gt;&lt;em&gt;ne supra crepidam&lt;/em&gt;&lt;/a&gt;
might not be the worst thing to do.&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Which, by the way, was far from being over for many folks at the
time of the invasion.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;The details are not important here.&amp;#160;&lt;a class="footnote-backref" href="#fnref:2"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;A wonderful quote from a &lt;a href="https://en.wikipedia.org/wiki/Thud!"&gt;wonderful
book&lt;/a&gt;, which, despite its
fantastical setting, shines a great light on our human affairs.&amp;#160;&lt;a class="footnote-backref" href="#fnref:3"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;I deliberately used that comparison because only the most
untruthful folks were denying the actual existence of the virus,
whereas in geopolitical conflicts, the lines are often very, very
murky.&amp;#160;&lt;a class="footnote-backref" href="#fnref:4"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>Ecce Homology on Bastian Grossenbacher Rieck's personal homepage</author><pubDate>Sun, 05 Nov 2023 17:00:00 GMT</pubDate><guid isPermaLink="true">https://bastian.rieck.me/blog/2023/posturing/</guid></item><item><title>Fixing Tor Browser accessibility</title><link>https://seirdy.one/notes/2023/11/05/fixing-tor-browser-accessibility/</link><description>&lt;p&gt;It looks like the Tor Browser is finally addressing some of the accessibility issues inherent to its fingerprinting resistance, starting with &lt;a href="https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/42226"&gt;Issue 42226: Reduce Customisability of default fonts and colours&lt;/a&gt;. The propsed fix of curating common “sets” of settings would add some entropy to everybody’s fingerprint but allow more people to use the browser overall, as it would support some degree of personalization (e.g. dark mode).&lt;/p&gt;&lt;p&gt;I welcome the change. Anonymity should be accessible.&lt;/p&gt;</description><author>All content on Seirdy’s Home</author><pubDate>Sun, 05 Nov 2023 15:34:53 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/notes/2023/11/05/fixing-tor-browser-accessibility/</guid></item><item><title>Design</title><link>https://lagomor.ph/info/design/</link><description>&lt;p&gt;Inspired by Gwern Branwen&amp;rsquo;s &lt;a href="https://gwern.net/design" rel="noopener noreferrer" target="_blank"&gt;design outline&lt;/a&gt;, this page documents the design philosophy behind not only this website, but also every other project I work on.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lagomor.ph&lt;/strong&gt; is implemented as a static website, compiled via &lt;a href="https://gohugo.io/" rel="noopener noreferrer" target="_blank"&gt;Hugo&lt;/a&gt; from Standard Markdown, and hosted on a cloud server in Toronto.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Indifference towards people and the reality in which they live is actually the one and only cardinal sin in design.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Dieter Rams&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It is designed to be readable, pleasing to the eye, and easy to navigate.
The logo, which is often the singular image on each page, was drawn by &lt;a href=""&gt;Rek Bell&lt;/a&gt;.&lt;/p&gt;</description><author>Home on Lagomorph</author><pubDate>Sun, 05 Nov 2023 08:00:00 GMT</pubDate><guid isPermaLink="true">https://lagomor.ph/info/design/</guid></item><item><title>No Hard Feelings</title><link>https://olshansky.info/movie/no_hard_feelings/</link><description>Olshansky's review of No Hard Feelings</description><author>🦉 olshansky 🦁</author><pubDate>Sun, 05 Nov 2023 03:16:21 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/movie/no_hard_feelings/</guid></item><item><title>Always get Google search results in English including Incognito mode</title><link>https://gourav.io/blog/force-google-english-results</link><description>keep Google searches in English while traveling, with a straightforward tip that works in any browser, incognito included</description><author>Gourav Goyal</author><pubDate>Sun, 05 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://gourav.io/blog/force-google-english-results</guid></item><item><title>First week of wellness challenge</title><link>https://benovermyer.com/blog/2023/11/first-week-of-wellness-challenge/</link><description>&lt;p&gt;The first week of my November 2023 wellness challenge went well! Going without
alcohol has given me back a ton of calories I would otherwise have gained, and
I'm feeling more energetic every morning, too.&lt;/p&gt;
&lt;p&gt;My first workout was on Wednesday morning at 5:00. It took me a bit to figure out
where the stations and machines were that I needed for my workout plan. Some things
I had to improvise a bit on; there was no cable row, so I just used the rowing
machine.&lt;/p&gt;
&lt;p&gt;My second workout on Friday morning was better. After blundering my way through
the first workout, I took time on Thursday to put together a gym bag. I brought
a log book and made sure to note down what weights I used for each exercise. I
was still a bit sore from Wednesday's workout, but by Saturday morning the soreness
from both sessions was gone.&lt;/p&gt;
&lt;p&gt;This week coming up will be the last week of the "warmup plan" specified by my
program. The weeks following will be more involved, and will be four days a week
instead of three.&lt;/p&gt;
&lt;p&gt;I've already started to lose a little weight. I'm looking forward to seeing
how far I can get this month.&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Sun, 05 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2023/11/first-week-of-wellness-challenge/</guid></item><item><title>what are authors even trying to do?</title><link>https://taylor.town/what-is-the-author-trying-to-do</link><description>&lt;p&gt;Rather than saying "bad movie lol",
&lt;a href="https://www.youtube.com/watch?v=vmI5qqIhEtY"&gt;Big Joel deconstructs &lt;em&gt;The Room&lt;/em&gt;&lt;/a&gt;
and asks the difficult question: &lt;strong&gt;what are authors even trying to do?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.reddit.com/r/Showerthoughts/comments/22rzxj/what_are_snails_even_trying_to_do/"&gt;&lt;img alt="what are snails even trying to do" src="https://taylor.town/snails.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This is a fundamental question that your high-school teachers failed to teach
you. While they quizzed you on Symbolism™ and Metaphor™ and Foreshadowing™, they
fumbled the underlying lesson: &lt;em&gt;people sometimes make stuff for reasons&lt;/em&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=bzThDWTAZYs"&gt;Can they understand the cat in &lt;em&gt;Stewart Little&lt;/em&gt;?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Dig for
&lt;a href="https://en.wikipedia.org/wiki/The_Worst_Idea_of_All_Time"&gt;authorial intent in &lt;em&gt;Grown Ups 2&lt;/em&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/@overanalyzingavatar"&gt;Overanalyze &lt;em&gt;Avatar: The Last Airbender&lt;/em&gt;.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Why did we watch
&lt;a href="https://www.youtube.com/watch?v=JsNm2YLrk30"&gt;a 2-hour documentary about a video game we've never heard of&lt;/a&gt;?&lt;/li&gt;
&lt;li&gt;Listen for
&lt;a href="https://www.youtube.com/watch?v=e7BkmF8CJpQ"&gt;leitmotif in &lt;em&gt;Lord of the Rings&lt;/em&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=h67AHvHMtxY"&gt;Become a film conspiracy theorist.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Engage in &lt;a href="https://www.youtube.com/@TerribleWritingAdvice"&gt;terrible writing&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Study &lt;a href="https://www.youtube.com/watch?v=3-2NH-NVePw"&gt;idiot plots&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=hEMqTPUWXqw"&gt;Is &lt;em&gt;300&lt;/em&gt; facist?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=8SlSG8d1oAk"&gt;&lt;em&gt;Meet the Robinsons&lt;/em&gt; was underrated.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Look for
&lt;a href="https://www.youtube.com/watch?v=6HZuSzlN2eI"&gt;inivisible choices in video games&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Avoid
&lt;a href="https://www.youtube.com/watch?v=3LWF5Oq6668"&gt;holes that were made for you&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=Vb-ajY8M6gI"&gt;Skin Theory in &lt;em&gt;Spongebob Squarepants&lt;/em&gt;.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Understand the
&lt;a href="https://www.youtube.com/watch?v=0OoLokmqo0A"&gt;temporal pincer maneuver&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=xSXofLK5hFQ"&gt;Soulja Boy provides his thoughts on &lt;em&gt;Braid&lt;/em&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Experience &lt;a href="https://www.youtube.com/watch?v=2AZ0I6tFvgg"&gt;visual comedy&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Find
&lt;a href="https://www.youtube.com/watch?v=VWz1E3oHd8w"&gt;ludditism and neophilia in &lt;em&gt;Jurassic Park&lt;/em&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;</description><author>taylor.town</author><pubDate>Sun, 05 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/what-is-the-author-trying-to-do</guid></item><item><title>Russian Woman Achieves 9x "Mythical" Man Month</title><link>https://taylor.town/woman-month</link><description>I have 22 children at the age of 26 and most of them were born in the space of a year.</description><author>taylor.town</author><pubDate>Sun, 05 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/woman-month</guid></item><item><title>How to use Dev Tools</title><link>https://river.me/blog/dev-tools/</link><description>Learn how to edit CSS interactively</description><author>River Writes - A MediaWiki Blog</author><pubDate>Sat, 04 Nov 2023 22:52:47 GMT</pubDate><guid isPermaLink="true">https://river.me/blog/dev-tools/</guid></item><item><title>Creating an OpenAPI generator from scratch : From YAML to JetBrains HTTP Client</title><link>https://lengrand.fr/creating-an-openapi-generator-from-scratch-from-yaml-to-jetbrains-http-client/</link><description>In this article, I'll be implementing an openapi generator from scratch so you can too! We'll be creating a very simple generator for the Jetbrains HTTP Client</description><author>Julien's DevRel corner</author><pubDate>Sat, 04 Nov 2023 19:40:33 GMT</pubDate><guid isPermaLink="true">https://lengrand.fr/creating-an-openapi-generator-from-scratch-from-yaml-to-jetbrains-http-client/</guid></item><item><title>Habits of great software engineers</title><link>https://vadimkravcenko.com/shorts/habits-of-great-software-engineers/</link><description>&lt;p&gt;Question:&lt;/p&gt;
&lt;/p&gt;
&lt;p&gt; Answer:&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://vadimkravcenko.com/shorts/habits-of-great-software-engineers/"&gt;Habits of great software engineers&lt;/a&gt; appeared first on &lt;a href="https://vadimkravcenko.com"&gt;Vadim Kravcenko&lt;/a&gt;.&lt;/p&gt;</description><author>Vadim Kravcenko</author><pubDate>Sat, 04 Nov 2023 13:08:19 GMT</pubDate><guid isPermaLink="true">https://vadimkravcenko.com/shorts/habits-of-great-software-engineers/</guid></item><item><title>Big Mouth: Season 7</title><link>https://olshansky.info/tv/big_mouth_season_7/</link><description>Olshansky's review of Big Mouth: Season 7</description><author>🦉 olshansky 🦁</author><pubDate>Sat, 04 Nov 2023 03:16:21 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/tv/big_mouth_season_7/</guid></item><item><title>A Market Research Colossus</title><link>https://magis.substack.com/p/a-market-research-colossus</link><description>Nielsen in A.C. Nielsen's own words</description><author>Magis</author><pubDate>Fri, 03 Nov 2023 22:29:13 GMT</pubDate><guid isPermaLink="true">https://magis.substack.com/p/a-market-research-colossus</guid></item><item><title>Being a minor AI public figure</title><link>https://www.swyx.io/being-a-minor-ai-public-figure</link><description>I was recently involved in moderating a chat with Kanjun Qiu of Imbue at the MIT AI conf: https://photos.google.com/share/AF1QipNJ9i78ICeg2YuemyAXmtStKvqr9l0Tao3xQWxmeAVjBALHnNnFvPXFlNSkdMfqA?pli=1&amp;amp;key=dTFRRHBTLVRZTEVCem0zal8tNVkxblh0V3k4VXhR</description><author>swyx's site RSS Feed</author><pubDate>Fri, 03 Nov 2023 22:21:53 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/being-a-minor-ai-public-figure</guid></item><item><title>Suggested readings week 44/2023</title><link>https://www.stefanocosta.org/suggested-readings-week-44-2023/</link><description>&lt;hr /&gt;&lt;h3 id="the-story-behind-the-business-model-canvas"&gt;&lt;a href="https://eiexchange.com/content/the-story-behind-the-business-model-canvas?ref=stefanocosta.org"&gt;The Story Behind the Business Model Canvas&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Alexander Osterwalder is best known as the inventor of the Business Model Canvas, a graphical and guided scheme for building a business model by gathering and grouping relevant information.&lt;/p&gt;
&lt;p&gt;He&amp;apos;s indeed a theorist of Business Models Onthology and Organization, and he&lt;/p&gt;</description><author>Food for Thought (by Stefano Costa)</author><pubDate>Fri, 03 Nov 2023 15:03:50 GMT</pubDate><guid isPermaLink="true">https://www.stefanocosta.org/suggested-readings-week-44-2023/</guid></item><item><title>Testing The PHASE M645-GFX E with Mamiya, Phase One, and Schneider Lenses</title><link>https://blog.maxg.io/testing-the-phase-m645-gfx-e-with-mamiya-phase-one-and-schneider-lenses/</link><description>&lt;p&gt;TL;DR: Works perfectly on newer Silver Ring and Blue Ring lenses, less perfect on older Mamiya lenses. Flare is another issue. Overall, it is really nice to use these lenses with the GFX100s, and get metadata and aperture control. &lt;/p&gt;&lt;p&gt;Recently, Kipon released a &lt;a href="https://kipon.com/product/m645-gfx-e/?ref=blog.maxg.io"&gt;new adapter&lt;/a&gt; for controlling the apeture&lt;/p&gt;</description><author>9μm Pixels</author><pubDate>Fri, 03 Nov 2023 11:11:07 GMT</pubDate><guid isPermaLink="true">https://blog.maxg.io/testing-the-phase-m645-gfx-e-with-mamiya-phase-one-and-schneider-lenses/</guid></item><item><title>Wrangling Table Bloat in Large PostgreSQL Tables</title><link>https://dylanpaulus.com/posts/2023/how-to-reduce-bloat-in-large-postgresql-tables/</link><description>&lt;p&gt;I worked with &lt;a href="https://www.timescale.com/"&gt;Timescale&lt;/a&gt; again to write an article on how table bloat effects large tables, and how we can fix it.&lt;/p&gt;
&lt;p&gt;We cover a range of topics including:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;What is table bloat?&lt;/li&gt;
  &lt;li&gt;Why is it an issue?&lt;/li&gt;
  &lt;li&gt;How to monitor table bloat?&lt;/li&gt;
  &lt;li&gt;What Vacuum and AutoVacuum is&lt;/li&gt;
  &lt;li&gt;Tips with tuning AutoVacuum&lt;/li&gt;
  &lt;li&gt;Table Partitioning&lt;/li&gt;
  &lt;li&gt;How Timescale chunks makes partitioning easier&lt;/li&gt;
  &lt;li&gt;And more!&lt;/li&gt;
&lt;/ul&gt;</description><author>Dylan Paulus' Blog</author><pubDate>Fri, 03 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://dylanpaulus.com/posts/2023/how-to-reduce-bloat-in-large-postgresql-tables/</guid></item><item><title>Rust for Dummies</title><link>https://rajeevnaruka.com/blog/rust-for-js-people</link><description>Unstructured notes to self on Rust. Peculiar stuff or just basics.</description><author>Rajeev Singh Naruka</author><pubDate>Fri, 03 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://rajeevnaruka.com/blog/rust-for-js-people</guid></item><item><title>Compressing Codebase Collocates</title><link>https://taylor.town/code-collocates</link><description>Replacing collocates with simpler substitutes is intuitively called "compression".</description><author>taylor.town</author><pubDate>Fri, 03 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/code-collocates</guid></item><item><title>Digital Desire Paths</title><link>https://taylor.town/digital-desire-paths</link><description>Good architects anticipate nature. They don't blame water for dripping. They don't blame people for peopling.</description><author>taylor.town</author><pubDate>Fri, 03 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/digital-desire-paths</guid></item><item><title>The technical problems with LLMs</title><link>https://macleodsawyer.com/blog/current-issues-with-llms/</link><description>&lt;p&gt;I think my biggest (technical, long-term) issues with current generation AI, primarily through LLMs such as ChatGPT, are the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You cannot readily replicate responses, which is the entire point of the scientific process. To ensure correct results for discovery and use is the ability to easily replicate results.&lt;/li&gt;
&lt;li&gt;They are primarily cloud based, which means you &lt;strong&gt;will not&lt;/strong&gt; be able to stand them up for testing in 1-5 years and replicate any sort of results or research papers that may be discovered now. Technical, direct, or anything else.&lt;/li&gt;
&lt;li&gt;They are primarily cloud based, which means any thing you throw at them, will not be able to be tuned with similar characteristics without massive manual testing as they are internally changed all the time.&lt;/li&gt;
&lt;li&gt;They are primarily cloud based, which means you cannot have any ownership or security in the knowledge, that they are safely utilizing your data.&lt;/li&gt;
&lt;li&gt;They are primarily cloud based, which means they can price you out of their system at any time.&lt;/li&gt;
&lt;li&gt;They are primarily cloud based, which means that they &lt;strong&gt;will&lt;/strong&gt; price you out of their system at any time.&lt;/li&gt;
&lt;li&gt;They are primarily cloud based, which means they will break your/their API, and there will be no recompense or ability to change providers.&lt;/li&gt;
&lt;li&gt;They are all primarily a private company, and if you build on their platform, then you have no platform of your own. Your business, is dependent on, and is &lt;em&gt;their&lt;/em&gt; business.&lt;/li&gt;
&lt;li&gt;You own nothing. They own all of it. No matter the legal contract you have with them will protect you from that.&lt;/li&gt;
&lt;li&gt;They are primarily cloud based, they are primarily private businesses, and they are anti-scientific development as they are black boxes of inputs, controls, and outputs.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Anything you develop on them now, will not work the same, if at all, in the future. You will have virtually, tangentially, literally, no legacy or agency.&lt;/p&gt;</description><author>Macleod Sawyer | Web Portal Feed</author><pubDate>Fri, 03 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://macleodsawyer.com/blog/current-issues-with-llms/</guid></item><item><title>Postgres HBA with a Tailscale network</title><link>https://ellie.wtf/notes/postgres-hba-tailscale/</link><description>&lt;p&gt;Following on with my &lt;a href="https://ellie.wtf/notes/postgres-on-zfs/"&gt;postgres on zfs&lt;/a&gt; setup, I needed to configure the auth so that my &lt;a href="https://ellie.wtf/notes/postgresql/"&gt;PostgreSQL&lt;/a&gt; replica could securely connect to the primary.&lt;/p&gt;
&lt;p&gt;The constraints here are that I'm not using a cloud private network, so need a VPN of some kind! I'm using &lt;a href="https://tailscale.com" rel="external"&gt;Tailscale&lt;/a&gt;, which is pretty much just wireguard made easy.&lt;/p&gt;
&lt;h2 id="firewall"&gt;Firewall&lt;/h2&gt;
&lt;p&gt;First up, following Tailscale's &lt;a href="https://tailscale.com/kb/1077/secure-server-ubuntu-18-04/" rel="external"&gt;docs&lt;/a&gt;. Make sure you don't lock yourself out! I'm also using tailscale SSH with 2fa.&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;ufw&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; allow in on tailscale0&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;ufw&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; default deny incoming&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;ufw&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; default allow outgoing&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;ufw&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; reload&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;systemctl&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; restart sshd&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="postgresql-conf"&gt;postgresql.conf&lt;/h2&gt;
&lt;p&gt;Next, we need to tell postgres to listen on all addresses. In your postgres config:&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;listen_addresses = '*'&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I wish I could just provide &lt;code&gt;tailscale0&lt;/code&gt;, the tailnet CIDR or... anything else. Alas. It's not that flexible, but at least we can lock things down with the firewall + hba. Don't skip the other steps!&lt;/p&gt;
&lt;h1 id="pg-hba"&gt;pg_hba&lt;/h1&gt;
&lt;p&gt;Next up we just need to setup pg_hba.conf to allow the login!&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;host    all             all             100.64.0.0/10           scram-sha-256&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where &lt;code&gt;100.64.0.0/10&lt;/code&gt; is the CIDR range used by Tailscale. Read more &lt;a href="https://tailscale.com/kb/1015/100.x-addresses/" rel="external"&gt;here&lt;/a&gt;.&lt;/p&gt;</description><author>Ellie's Notes</author><pubDate>Fri, 03 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ellie.wtf/notes/postgres-hba-tailscale/</guid></item><item><title>Bunny Sync: Handy Upload Helper</title><link>https://sergeykibish.com/blog/bunny-sync-handy-upload-helper/</link><description>Sync local files effortlessly with Bunny Sync, a simple CLI tool designed for Bunny Storage users. No FTP fuss, no waiting for APIs - just quick and straightforward syncing.</description><author>Sergey's Blog</author><pubDate>Thu, 02 Nov 2023 21:52:04 GMT</pubDate><guid isPermaLink="true">https://sergeykibish.com/blog/bunny-sync-handy-upload-helper/</guid></item><item><title>The Baseline Fallacy in Defensive Cyber Operations</title><link>https://zacs.site/blog/the-baseline-fallacy-in-defensive-cyber-operations.html</link><description>&lt;p&gt;Both civilian &lt;a href="https://www.sans.org/white-papers/detecting-unauthorized-behavior-from-legitimate-accounts/"&gt;cybersecurity practitioners&lt;/a&gt; and military defensive cyber operations personnel continue to push a similar idea, that to identify malicious activity one must &amp;#8220;just get a baseline and then flag anything different.&amp;#8221; If only! I call this the Baseline Fallacy:&lt;/p&gt;
                &lt;p&gt;&lt;a href="https://zacs.site/blog/the-baseline-fallacy-in-defensive-cyber-operations.html"&gt;Permalink.&lt;/a&gt;&lt;/p&gt;</description><author>Zac Szewczyk</author><pubDate>Thu, 02 Nov 2023 20:25:05 GMT</pubDate><guid isPermaLink="true">https://zacs.site/blog/the-baseline-fallacy-in-defensive-cyber-operations.html</guid></item><item><title>We decided to open a cafe for a day. This happened</title><link>https://www.ednevsky.blog/p/we-decided-to-open-a-cafe-for-a-day</link><description>If you're a knowledge worker and ever dreamed of doing something like this, read along. This is a full story/guide of how we approached this</description><author>Ednevsky Blog</author><pubDate>Thu, 02 Nov 2023 15:01:00 GMT</pubDate><guid isPermaLink="true">https://www.ednevsky.blog/p/we-decided-to-open-a-cafe-for-a-day</guid></item><item><title>Unstable: Season 1</title><link>https://olshansky.info/tv/unstable_season_1/</link><description>Olshansky's review of Unstable: Season 1</description><author>🦉 olshansky 🦁</author><pubDate>Thu, 02 Nov 2023 03:52:38 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/tv/unstable_season_1/</guid></item><item><title>Inspiration from Fall 2023—and a reader survey!</title><link>https://bowendwelle.substack.com/p/inspiration-from-2023-10</link><description>Sophie Strand, Ben McGrath, John and Monica Chapman, Jessica Carew Kraft, Kendra Atleework, Paul Kingsnorth, Stewart Brand, Holly Payne, Bari Weiss, Scott Galloway, Richard V. Reeves, Scott Lyons</description><author>An Ordinary Disaster</author><pubDate>Thu, 02 Nov 2023 02:15:15 GMT</pubDate><guid isPermaLink="true">https://bowendwelle.substack.com/p/inspiration-from-2023-10</guid></item><item><title>Add Cloudflare Web Analytics to Next.js site</title><link>https://gourav.io/blog/cloudflare-analytics-nextjs</link><description>Add Cloudflare Web Analytics to Next.js site</description><author>Gourav Goyal</author><pubDate>Thu, 02 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://gourav.io/blog/cloudflare-analytics-nextjs</guid></item><item><title>Návod na prežitie pre bejby</title><link>https://vit.baisa.cz/books/navod-na-prezitie-pre-bejby/</link><description>souhrn poznatků od youtubera</description><author>Vít Baisa</author><pubDate>Thu, 02 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://vit.baisa.cz/books/navod-na-prezitie-pre-bejby/</guid></item><item><title>How to opt-out of ChatGPT training without clearing conversation history</title><link>https://taylor.town/chatgpt-opt-out</link><description>OpenAI provides a Google Form for opting out of data collection on ChatGPT.</description><author>taylor.town</author><pubDate>Thu, 02 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/chatgpt-opt-out</guid></item><item><title>My Podcast Subscriptions</title><link>https://taylor.town/podcasts</link><description>&lt;p&gt;Podcasts are great!
&lt;a href="https://taylor.town/essays/the-problem-with-podcasts"&gt;Except when they're not&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I highly recommend &lt;a href="https://overcast.fm"&gt;overcast.fm&lt;/a&gt; for listening to podcasts.&lt;/p&gt;
&lt;h2 id="my-current-rotation"&gt;My Current Rotation&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th id="" style="text-align: right;"&gt;&lt;/th&gt;
&lt;th id="" style="text-align: left;"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://99percentinvisible.org"&gt;99% Invisible&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://www.radiotopia.fm/podcasts/articles-of-interest"&gt;Articles of Interest&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://conversationswithtyler.com"&gt;Conversations with Tyler&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://sive.rs"&gt;Derek Sivers&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://www.radiolab.org"&gt;Radiolab&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://thememorypalace.us"&gt;The Memory Palace&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://www.relay.fm/cortex/"&gt;Cortex&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://www.pushkin.fm/podcasts/whats-your-problem"&gt;What's Your Problem?&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://twitter.com/sw_unscripted?lang=en"&gt;Software Unscripted&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Bingeworthy series that are completed or on indefinite hiatus:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th id="" style="text-align: right;"&gt;&lt;/th&gt;
&lt;th id="" style="text-align: left;"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://stownpodcast.org/"&gt;S-Town&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://www.wnycstudios.org/podcasts/anthropocene-reviewed"&gt;The Anthropocene Reviewed&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://oxide.computer/podcast/on-the-metal"&gt;On the Metal&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://mailchimp.com/presents/podcast/sonic-symbolism"&gt;Björk: Sonic Symbolism&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://podcasts.apple.com/us/podcast/my-year-in-mensa/id1494245979"&gt;My Year in Mensa&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://podcasts.apple.com/us/podcast/the-trojan-horse-affair/id1600251425"&gt;The Trojan Horse Affair&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://www.hellointernet.fm/"&gt;Hello Internet&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Support these cool tech podcasts produced by my friends:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th id="" style="text-align: right;"&gt;&lt;/th&gt;
&lt;th id="" style="text-align: left;"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;♥&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://www.devtools.fm/"&gt;devtools.fm&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;♥&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://changelog.com/podcast"&gt;The Changelog&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;♥&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://elmtown.simplecast.com/"&gt;Elm Town&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;♥&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://futureofcoding.org/podcast"&gt;Future of Coding&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;♥&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://podcasts.apple.com/us/podcast/hest/id1484697758"&gt;Hest&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;♥&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://feeds.resonaterecordings.com/software-unscripted"&gt;Software Unscripted&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;♥&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;a href="https://www.todepond.com/"&gt;TODEPOND PODCAST&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="honorable-mentions"&gt;Honorable Mentions&lt;/h2&gt;
&lt;p&gt;Series that I enjoyed at some point in the past:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th id="" style="text-align: right;"&gt;&lt;/th&gt;
&lt;th id="" style="text-align: left;"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Against the Rules with Michael Lewis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Ahead of its Time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Akimbo: A Podcast from Seth Godin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;American Innovations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Brains&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Cautionary Tales&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Chemistry For Your Life&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Common Sense with Dan Carlin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Conan O'Brien Needs A Friend&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Dan Carlin's Hardcore History&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Darknet Diaries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Dear Hank &amp;amp; Jon&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Deep Questions with Cal Newport&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Endless Thread&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Epic Gardening&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Everything Everywhere Daily&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Everything is Alive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Experimental History&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Freakonomics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Harmontown&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Hey Riddle Riddle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Land by Hand&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Monday Morning Podcast with Bill Burr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;More Perfect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Nice Try!&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oxford University Computer Science&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oxide &amp;amp; Friends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Patented: History of Inventions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Planet Money&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Reasonably Sound&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Reconcilable Differences&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Reply All&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Revisionist History&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Stuff You Should Know&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;★★&lt;/td&gt;
&lt;td style="text-align: left;"&gt;The Economics of Everyday Things&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;The Joy of Why&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;The Permaculture Podcast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;The Peter Attia Drive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;The Strong Towns Podcast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;The Tim Ferriss Show&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Trailblazers with Walter Isaacson&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</description><author>taylor.town</author><pubDate>Thu, 02 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/podcasts</guid></item><item><title>CDC Use Cases: 7 Ways to Put CDC to Work</title><link>https://www.morling.dev/blog/cdc-use-cases/</link><description>&lt;div class="toc" id="toc"&gt;
&lt;div id="toctitle"&gt;Table of Contents&lt;/div&gt;
&lt;ul class="sectlevel1"&gt;
&lt;li&gt;&lt;a href="#_what_is_cdc"&gt;What is CDC?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_cdc_tools"&gt;CDC Tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_analytics_data_platforms"&gt;Analytics Data Platforms&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_application_caches"&gt;Application Caches&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_full_text_search"&gt;Full-Text Search&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_audit_logs"&gt;Audit Logs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_continuous_queries"&gt;Continuous Queries&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_microservices_data_exchange"&gt;Microservices Data Exchange&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_monolith_to_microservices_migration"&gt;Monolith-to-Microservices Migration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_summary"&gt;Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;em&gt;This post originally appeared on the &lt;a href="https://www.decodable.co/blog/cdc-use-cases"&gt;Decodable blog&lt;/a&gt;. All rights reserved.&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Change Data Capture (CDC) is a powerful tool in data engineering and has seen a tremendous uptake in organizations of all kinds over the last few years.
This is because it enables the tight integration of transactional databases into many other systems in your business at a very low latency.&lt;/p&gt;
&lt;/div&gt;</description><author>Gunnar Morling</author><pubDate>Thu, 02 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.morling.dev/blog/cdc-use-cases/</guid></item><item><title>Jobs, swords, and doors</title><link>https://jordemort.dev/blog/jobs-swords-and-doors/</link><description>What have I been up to lately?</description><author>Jordan Webb's blog</author><pubDate>Wed, 01 Nov 2023 22:33:37 GMT</pubDate><guid isPermaLink="true">https://jordemort.dev/blog/jobs-swords-and-doors/</guid></item><item><title>What is the average length of a queue of cars?</title><link>https://e-dorigatti.github.io/math/2023/11/01/queue-length.html</link><description>Some time ago I was driving on a twisty mountain road, stuck in a slow-moving queue of cars as it was impossible to overtake safely. Out of boredom, I was wondering how many cars were in the queue, and, more generally, what would be the average length of queues in this road. Let’s find out!</description><author>Emilio’s Blog</author><pubDate>Wed, 01 Nov 2023 12:00:00 GMT</pubDate><guid isPermaLink="true">https://e-dorigatti.github.io/math/2023/11/01/queue-length.html</guid></item><item><title>E-Bike to Lenovo Power Supply</title><link>lenovo_ebike_adapter.html</link><description>Does your CAD laptop run it's battery dry in less than 3 hrs? Do you like working off in the hills and want access to a longer runtimes on your power hungry desktop-replacement machine?</description><author>transistor-man.com</author><pubDate>Wed, 01 Nov 2023 10:38:38 GMT</pubDate><guid isPermaLink="true">lenovo_ebike_adapter.html</guid></item><item><title>Inspiration</title><link>https://lovebloodrhetoric.com/2023/10/31/inspiration/</link><description>“A little warm for a fire, don’t you think?” “It’s for inspiration, not warmth.” “Well, it’ll burn better if you stop poking it.” “I’m tickling it. To amuse it. You know, Shakespeare? Oh, to amuse a fire?” “I don’t think you’re remembering that right.” “And if tickling doesn’t work, I’ll try poetry or knock-knock jokes.” [&amp;#8230;]</description><author>Love, Blood &amp;amp; Rhetoric</author><pubDate>Wed, 01 Nov 2023 05:16:54 GMT</pubDate><guid isPermaLink="true">https://lovebloodrhetoric.com/2023/10/31/inspiration/</guid></item><item><title>Data Sovereignty In Aws Rds</title><link>/2023/11/01/Data-Sovereignty-in-AWS-RDS.html</link><description>In the era of global data flows, maintaining compliance with data sovereignty laws and managing cross-border data transfers can be challenging. Cloud infrastructures often span multiple geographies, and as data moves across these boundaries, it becomes subject to diverse legal frameworks and regulations (such as the General Data Protection Regulation or GDPR in Europe). This technical blueprint provides a guide on using RDS to address these challenges.</description><author>rrich.io</author><pubDate>Wed, 01 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">/2023/11/01/Data-Sovereignty-in-AWS-RDS.html</guid></item><item><title>if it talks like a therapist...</title><link>https://taylor.town/talk-like-a-therapist</link><description>They speak fearlessly but fairly. They're strong, yet nonthreatening. They push back while maintaining peace. I yearn for this powerless power.</description><author>taylor.town</author><pubDate>Wed, 01 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/talk-like-a-therapist</guid></item><item><title>Writing a storage engine for Postgres: an in-memory Table Access Method</title><link>http://notes.eatonphil.com/2023-11-01-postgres-table-access-methods.html</link><description>&lt;p&gt;With &lt;a href="https://www.postgresql.org/docs/release/12.0/"&gt;Postgres 12&lt;/a&gt;,
released in 2019, it became possible to &lt;a href="https://www.pgcon.org/2019/schedule/attachments/536_pgcon2019_pluggable_table_AM_V1.3.pdf"&gt;swap out Postgres's storage
engine&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is a feature MySQL has supported for a long time. There are at
least &lt;a href="https://github.com/eatonphil/pgtam"&gt;8 different&lt;/a&gt; &lt;em&gt;built-in&lt;/em&gt;
engines you can pick from. &lt;a href="https://myrocks.io/"&gt;MyRocks&lt;/a&gt;, MySQL on
RocksDB, is another popular third-party distribution.&lt;/p&gt;
&lt;p&gt;I assume there will be a renaissance of Postgres storage engines. To
date, the efforts are
nascent. &lt;a href="https://github.com/orioledb/orioledb"&gt;OrioleDB&lt;/a&gt; and &lt;a href="https://github.com/citusdata/citus/blob/main/src/backend/columnar/README.md"&gt;Citus
Columnar&lt;/a&gt;
are two promising third-party table access methods being actively
developed.&lt;/p&gt;
&lt;h3 id="why-alternative-storage-engines?"&gt;Why alternative storage engines?&lt;/h3&gt;&lt;p&gt;The ability to swap storage engines is useful because different
workloads sometimes benefit from different storage
approaches. Analytics workloads and columnar storage layouts &lt;a href="https://docs.aws.amazon.com/redshift/latest/dg/c_columnar_storage_disk_mem_mgmnt.html"&gt;go well
together&lt;/a&gt;. Write-heavy
workloads and LSM trees &lt;a href="https://github.com/wiredtiger/wiredtiger/wiki/Btree-vs-LSM"&gt;go well
together&lt;/a&gt;. And
some people like in-memory storage for running integration tests.&lt;/p&gt;
&lt;p&gt;By swapping out only the storage engine, you get the benefit of the
rest of the Postgres or MySQL infrastructure. The query language, the
wire protocol, the ecosystem, etc.&lt;/p&gt;
&lt;h3 id="why-not-foreign-data-wrappers?"&gt;Why not foreign data wrappers?&lt;/h3&gt;&lt;p&gt;Very little has been written about the difference between foreign data
wrappers (FDWs) and table access methods. Table access methods seems
to be the lower-level layer where presumably you get better
performance and cleaner integration. But there is clearly overlap
between these two extension options.&lt;/p&gt;
&lt;p&gt;For example there is a &lt;a href="https://github.com/ildus/clickhouse_fdw"&gt;FDW for
ClickHouse&lt;/a&gt; so when you
create tables and rows and query the tables you are really creating
and querying rows in a ClickHouse server. Similarly there's a &lt;a href="https://github.com/vidardb/pgrocks-fdw"&gt;FDW for
RocksDB&lt;/a&gt;. And Citus's columnar
engine works
&lt;a href="https://www.citusdata.com/blog/2021/03/06/citus-10-columnar-compression-for-postgres/#:~:text=What%20About%20cstore_fdw%3F"&gt;either&lt;/a&gt;
as a foreign data wrapper or a table access method.&lt;/p&gt;
&lt;p&gt;The Citus page draws the clearest distinction between FDWs and table
access methods, but even that page is vague. Performance doesn't seem
to be the main difference. Closer integration, and thus the ability to
look more like vanilla Postgres from the outside, seems to be the
gist.&lt;/p&gt;
&lt;p&gt;In any case, I wanted to explore the table access method API.&lt;/p&gt;
&lt;h3 id="digging-in"&gt;Digging in&lt;/h3&gt;&lt;p&gt;I haven't written Postgres extensions before and I've never written C
professionally. If you're familiar with Postgres internals or C and
notice something funky, please &lt;a href="mailto:me@eatonphil.com"&gt;let me know&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;It turns out that almost no one has written how to implement the
minimal table access methods for various storage engine operations. So
after quite a bit of stumbling to get the basics of an in-memory
storage engine working, I'm going to walk you through my approach.&lt;/p&gt;
&lt;p&gt;This is prototype-quality code which hopefully will be a useful base
for further exploration.&lt;/p&gt;
&lt;p&gt;All code for this post is &lt;a href="https://github.com/eatonphil/pgtam"&gt;available on
GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="a-debug-postgres-build"&gt;A debug Postgres build&lt;/h3&gt;&lt;p&gt;First off, let's make a &lt;a href="https://wiki.postgresql.org/wiki/Developer_FAQ#Compile-time"&gt;debug
build&lt;/a&gt; of
Postgres.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;clone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="o"&gt;://&lt;/span&gt;&lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;
&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# An arbitrary commit from `master` after Postgres 16 I am on&lt;/span&gt;
&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;849172ff4883d44168f96f39d3fde96d0aa34c99&lt;/span&gt;
&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;
&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="k"&gt;enable&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;cassert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="k"&gt;enable&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CFLAGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-ggdb -Og -g3 -fno-omit-frame-pointer&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;make&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;j8&lt;/span&gt;
&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;make&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;install&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This will install Postgres binaries (e.g. &lt;code&gt;psql&lt;/code&gt;, &lt;code&gt;pg_ctl&lt;/code&gt;, &lt;code&gt;initdb&lt;/code&gt;,
&lt;code&gt;pg_config&lt;/code&gt;) into &lt;code&gt;/usr/local/pgsql/bin&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I'm going to reference those absolute paths throughout this post
because you might have a system (package manager) install of Postgres
already.&lt;/p&gt;
&lt;p&gt;Let's create a database and start up this debug build:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/initdb&lt;span class="w"&gt; &lt;/span&gt;test-db
&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/pg_ctl&lt;span class="w"&gt; &lt;/span&gt;-D&lt;span class="w"&gt; &lt;/span&gt;test-db&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;logfile&lt;span class="w"&gt; &lt;/span&gt;start
&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="extension-infrastructure"&gt;Extension infrastructure&lt;/h3&gt;&lt;p&gt;Since we installed Postgres from scratch,
&lt;code&gt;/usr/local/pgsql/bin/pg_config&lt;/code&gt; will supply all of the infrastructure
we need.&lt;/p&gt;
&lt;p&gt;The "infrastructure" is basically just
&lt;a href="https://www.postgresql.org/docs/current/extend-pgxs.html"&gt;PGXS&lt;/a&gt;:
Postgres Makefile utilities.&lt;/p&gt;
&lt;p&gt;It's convention-heavy. So in a new &lt;code&gt;Makefile&lt;/code&gt; for this project we'll
specify:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;MODULES&lt;/code&gt;: Any C sources to build, without the &lt;code&gt;.c&lt;/code&gt; file extension&lt;/li&gt;
&lt;li&gt;&lt;code&gt;EXTENSION&lt;/code&gt;: Extension metadata file, without the &lt;code&gt;.control&lt;/code&gt; file extension&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DATA&lt;/code&gt;: A SQL file that is executed when the extension is loaded, this time with the &lt;code&gt;.sql&lt;/code&gt; extension&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;MODULES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pgtam
&lt;span class="nv"&gt;EXTENSION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pgtam
&lt;span class="nv"&gt;DATA&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pgtam--0.0.1.sql

&lt;span class="nv"&gt;PG_CONFIG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/usr/local/pgsql/bin/pg_config
&lt;span class="nv"&gt;PGXS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;shell&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;PG_CONFIG&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--pgxs&lt;span class="k"&gt;)&lt;/span&gt;
&lt;span class="cp"&gt;include $(PGXS)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The final three lines set up the PGXS Makefile library based on the
particular installed Postgres build we want to build the extension
against and install the extension to.&lt;/p&gt;
&lt;p&gt;PGXS gives us a few important targets like &lt;code&gt;make distclean&lt;/code&gt;, &lt;code&gt;make&lt;/code&gt;,
and &lt;code&gt;make install&lt;/code&gt; we'll use later on.&lt;/p&gt;
&lt;h4 id="&amp;lt;code&amp;gt;pgtam.c&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;pgtam.c&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;A minimal C file that registers a function capable of serving as a
table access method is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;postgres.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;fmgr.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;access/tableam.h&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;PG_MODULE_MAGIC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TableAmRoutine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_methods&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;T_TableAmRoutine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;PG_FUNCTION_INFO_V1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mem_tableam_handler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Datum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;mem_tableam_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PG_FUNCTION_ARGS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;PG_RETURN_POINTER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;memam_methods&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p class="note"&gt;
  If you want to read about extension basics without the complexity of
  table access methods, you can find a complete, minimal Postgres
  extension I wrote to validate the
  infrastructure &lt;a href="https://github.com/eatonphil/pgext-101"&gt;here&lt;/a&gt;. Or
  you can follow a
  &lt;a href="https://github.com/IshaanAdarsh/Postgres-extension-tutorial/blob/main/SGML/intro_and_toc.md"&gt;larger
    tutorial&lt;/a&gt;.
&lt;/p&gt;&lt;p&gt;The workflow for registering a table access method is to first run
&lt;code&gt;CREATE EXTENSION pgtam&lt;/code&gt;. This assumes &lt;code&gt;pgtam&lt;/code&gt; is an extension that
has a function that returns a &lt;code&gt;TableAmRoutine&lt;/code&gt; struct instance, a
table of table access methods.&lt;/p&gt;
&lt;p&gt;Then you must run &lt;code&gt;CREATE ACCESS METHOD mem TYPE TABLE HANDLER
mem_tableam_handler&lt;/code&gt;. And finally you can use the access method when
creating a table with &lt;code&gt;USING mem&lt;/code&gt;: &lt;code&gt;CREATE TABLE x(a INT) USING mem&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id="&amp;lt;code&amp;gt;pgtam.control&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;pgtam.control&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;This file contains extension metadata. At a minimum, the version of
the extension and the filename for the extension where it should be
installed.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;default_version = '0.0.1'
module_pathname = '$libdir/pgtam'
&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id="&amp;lt;code&amp;gt;pgtam--0.0.1.sql&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;pgtam--0.0.1.sql&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;Finally, in &lt;code&gt;pgtam--0.0.1.sql&lt;/code&gt; (which is executed when we call &lt;code&gt;CREATE
EXTENSION pgtam&lt;/code&gt;), we register the handler function as a foreign
function, and then we register the function as an access method.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;OR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;REPLACE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FUNCTION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mem_tableam_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURNS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;table_am_handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'pgtam'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'mem_tableam_handler'&lt;/span&gt;
&lt;span class="k"&gt;LANGUAGE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;STRICT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ACCESS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;METHOD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TYPE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;HANDLER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mem_tableam_handler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id="build"&gt;Build&lt;/h4&gt;&lt;p&gt;Now that we've got all the pieces in place, we can build and install
the extension.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;make
$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's add a &lt;code&gt;test.sql&lt;/code&gt; script to exercise the extension:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;DROP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;EXTENSION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;EXISTS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pgtam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;EXTENSION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pgtam&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And run it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
&lt;span class="go"&gt;DROP EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;psql:test.sql:3: server closed the connection unexpectedly&lt;/span&gt;
&lt;span class="go"&gt;        This probably means the server terminated abnormally&lt;/span&gt;
&lt;span class="go"&gt;        before or while processing the request.&lt;/span&gt;
&lt;span class="go"&gt;psql:test.sql:3: error: connection to server was lost&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ok, so &lt;code&gt;psql&lt;/code&gt; crashed! Let's look at the server logs. When we started
Postgres with &lt;code&gt;pg_ctl&lt;/code&gt; we specified the log file as &lt;code&gt;logfile&lt;/code&gt; in the
directory where we ran &lt;code&gt;pg_ctl&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If we look through it we'll spot an assertion failure:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;Assert&lt;span class="w"&gt; &lt;/span&gt;logfile
&lt;span class="go"&gt;TRAP: failed Assert(&amp;quot;routine-&amp;gt;scan_begin != NULL&amp;quot;), File: &amp;quot;tableamapi.c&amp;quot;, Line: 52, PID: 2906922&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That's a great sign! This is Postgres's debug infrastructure helping
to make sure the table access method is correctly implemented.&lt;/p&gt;
&lt;h3 id="table-access-method-stubs"&gt;Table access method stubs&lt;/h3&gt;&lt;p&gt;The next step is to add function stubs for all the non-optional
methods of the &lt;a href="https://github.com/postgres/postgres/blob/849172ff4883d44168f96f39d3fde96d0aa34c99/src/include/access/tableam.h#L282"&gt;&lt;code&gt;TableAmRoutine&lt;/code&gt;
struct&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I've done all the work for you already so you can just copy this over
the existing &lt;code&gt;pgtam.c&lt;/code&gt;. It's a big file, but don't worry. There's
nothing to explain. Just a bunch of blank functions returning default
values when required.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;postgres.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;fmgr.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;access/tableam.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;access/heapam.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;nodes/execnodes.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;catalog/index.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;commands/vacuum.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;utils/builtins.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;executor/tuptable.h&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;PG_MODULE_MAGIC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TableAmRoutine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_methods&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlotOps&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_slot_callbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_beginscan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nkeys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;ScanKeyData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ParallelTableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parallel_scan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;uint32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_rescan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sscan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;ScanKeyData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;set_params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allow_strat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allow_sync&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allow_pagemode&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_endscan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sscan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_getnextslot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sscan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ScanDirection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;IndexFetchTableData&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_index_fetch_begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_index_fetch_reset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IndexFetchTableData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_index_fetch_end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IndexFetchTableData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_index_fetch_tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;IndexFetchTableData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ItemPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;call_again&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;all_dead&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_tuple_insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;CommandId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;BulkInsertState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bistate&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_tuple_insert_speculative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;CommandId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;BulkInsertState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bistate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;uint32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;specToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_tuple_complete_speculative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;uint32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;specToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;succeeded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_multi_insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;slots&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ntuples&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;CommandId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;BulkInsertState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bistate&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TM_Result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_tuple_delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ItemPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;CommandId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;crosscheck&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TM_FailureData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tmfd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;changingPart&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TM_Result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TM_Result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_tuple_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ItemPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;otid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;CommandId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;crosscheck&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TM_FailureData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tmfd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;LockTupleMode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;lockmode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TU_UpdateIndexes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;update_indexes&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TM_Result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TM_Result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_tuple_lock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ItemPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;CommandId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;LockTupleMode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;LockWaitPolicy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wait_policy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;uint8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TM_FailureData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tmfd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TM_Result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_fetch_row_version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ItemPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_get_latest_tid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sscan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ItemPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tid&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_tuple_tid_valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ItemPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_tuple_satisfies_snapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TransactionId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_index_delete_tuples&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TM_IndexDeleteOp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;delstate&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TransactionId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_relation_set_new_filelocator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RelFileLocator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;newrlocator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;persistence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TransactionId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;freezeXid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;MultiXactId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;minmulti&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_relation_nontransactional_truncate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_relation_copy_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RelFileLocator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;newrlocator&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_relation_copy_for_cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OldHeap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NewHeap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OldIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;use_sort&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TransactionId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OldestXmin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TransactionId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;xid_cutoff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;MultiXactId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;multi_cutoff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;num_tuples&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tups_vacuumed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tups_recently_dead&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_vacuum_rel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;VacuumParams&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;BufferAccessStrategy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bstrategy&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_scan_analyze_next_block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;BlockNumber&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;blockno&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;BufferAccessStrategy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bstrategy&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_scan_analyze_next_tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TransactionId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OldestXmin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;liverows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;deadrows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_index_build_range_scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;heapRelation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;indexRelation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;IndexInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;indexInfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allow_sync&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;anyvisible&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;BlockNumber&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;start_blockno&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;BlockNumber&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;numblocks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;IndexBuildCallback&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;callback_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_index_validate_scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;heapRelation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;indexRelation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;IndexInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;indexInfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ValidateIndexState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_relation_needs_toast_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Oid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_relation_toast_am&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Oid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_fetch_toast_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;toastrel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Oid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;valueid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;int32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;attrsize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;int32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sliceoffset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;int32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;slicelength&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;varlena&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_estimate_rel_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;int32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;attr_widths&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;BlockNumber&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tuples&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;allvisfrac&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_scan_sample_next_block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SampleScanState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;scanstate&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_scan_sample_next_tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;SampleScanState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;scanstate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TableAmRoutine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_methods&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;T_TableAmRoutine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slot_callbacks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_slot_callbacks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scan_begin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_beginscan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scan_end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_endscan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scan_rescan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_rescan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scan_getnextslot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_getnextslot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parallelscan_estimate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;table_block_parallelscan_estimate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parallelscan_initialize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;table_block_parallelscan_initialize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parallelscan_reinitialize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;table_block_parallelscan_reinitialize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index_fetch_begin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_index_fetch_begin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index_fetch_reset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_index_fetch_reset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index_fetch_end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_index_fetch_end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index_fetch_tuple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_index_fetch_tuple&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tuple_insert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_tuple_insert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tuple_insert_speculative&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_tuple_insert_speculative&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tuple_complete_speculative&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_tuple_complete_speculative&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;multi_insert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_multi_insert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tuple_delete&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_tuple_delete&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tuple_update&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_tuple_update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tuple_lock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_tuple_lock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tuple_fetch_row_version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_fetch_row_version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tuple_get_latest_tid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_get_latest_tid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tuple_tid_valid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_tuple_tid_valid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tuple_satisfies_snapshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_tuple_satisfies_snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index_delete_tuples&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_index_delete_tuples&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relation_set_new_filelocator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_relation_set_new_filelocator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relation_nontransactional_truncate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_relation_nontransactional_truncate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relation_copy_data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_relation_copy_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relation_copy_for_cluster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_relation_copy_for_cluster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relation_vacuum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_vacuum_rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scan_analyze_next_block&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_scan_analyze_next_block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scan_analyze_next_tuple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_scan_analyze_next_tuple&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index_build_range_scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_index_build_range_scan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index_validate_scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_index_validate_scan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relation_size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;table_block_relation_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relation_needs_toast_table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_relation_needs_toast_table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relation_toast_am&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_relation_toast_am&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relation_fetch_toast_slice&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_fetch_toast_slice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relation_estimate_size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_estimate_rel_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scan_sample_next_block&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_scan_sample_next_block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scan_sample_next_tuple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_scan_sample_next_tuple&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;PG_FUNCTION_INFO_V1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mem_tableam_handler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;Datum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;mem_tableam_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PG_FUNCTION_ARGS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;PG_RETURN_POINTER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;memam_methods&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's build and test it!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
&lt;span class="go"&gt;psql:test.sql:1: NOTICE:  drop cascades to table x&lt;/span&gt;
&lt;span class="go"&gt;DROP EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE TABLE&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Hey we're getting somewhere! It successfully created the table with
our custom table access method.&lt;/p&gt;
&lt;h3 id="querying-rows"&gt;Querying rows&lt;/h3&gt;&lt;p&gt;Next, let's try querying the table by adding a &lt;code&gt;SELECT a FROM x&lt;/code&gt; to
&lt;code&gt;test.sql&lt;/code&gt; and running it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
&lt;span class="go"&gt;psql:test.sql:1: NOTICE:  drop cascades to table x&lt;/span&gt;
&lt;span class="go"&gt;DROP EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE TABLE&lt;/span&gt;
&lt;span class="go"&gt;psql:test.sql:6: server closed the connection unexpectedly&lt;/span&gt;
&lt;span class="go"&gt;        This probably means the server terminated abnormally&lt;/span&gt;
&lt;span class="go"&gt;        before or while processing the request.&lt;/span&gt;
&lt;span class="go"&gt;psql:test.sql:6: error: connection to server was lost&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This time there's nothing in &lt;code&gt;logfile&lt;/code&gt; that helps:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;tail&lt;span class="w"&gt; &lt;/span&gt;-n15&lt;span class="w"&gt; &lt;/span&gt;logfile
&lt;span class="go"&gt;2023-11-01 18:43:32.449 UTC [2906199] LOG:  database system is ready to accept connections&lt;/span&gt;
&lt;span class="go"&gt;2023-11-01 18:58:32.572 UTC [2907997] LOG:  checkpoint starting: time&lt;/span&gt;
&lt;span class="go"&gt;2023-11-01 18:58:35.305 UTC [2907997] LOG:  checkpoint complete: wrote 28 buffers (0.2%); 0 WAL file(s) added, 0 removed, 0 recycled; write=2.712 s, sync=0.015 s, total=2.733 s; sync files=23, longest=0.004 s, average=0.001 s; distance=128 kB, estimate=150 kB; lsn=0/15F88E0, redo lsn=0/15F8888&lt;/span&gt;
&lt;span class="go"&gt;2023-11-01 19:08:14.485 UTC [2906199] LOG:  server process (PID 2908242) was terminated by signal 11: Segmentation fault&lt;/span&gt;
&lt;span class="go"&gt;2023-11-01 19:08:14.485 UTC [2906199] DETAIL:  Failed process was running: SELECT a FROM x;&lt;/span&gt;
&lt;span class="go"&gt;2023-11-01 19:08:14.485 UTC [2906199] LOG:  terminating any other active server processes&lt;/span&gt;
&lt;span class="go"&gt;2023-11-01 19:08:14.486 UTC [2906199] LOG:  all server processes terminated; reinitializing&lt;/span&gt;
&lt;span class="go"&gt;2023-11-01 19:08:14.508 UTC [2908253] LOG:  database system was interrupted; last known up at 2023-11-01 18:58:35 UTC&lt;/span&gt;
&lt;span class="go"&gt;2023-11-01 19:08:14.518 UTC [2908253] LOG:  database system was not properly shut down; automatic recovery in progress&lt;/span&gt;
&lt;span class="go"&gt;2023-11-01 19:08:14.519 UTC [2908253] LOG:  redo starts at 0/15F8888&lt;/span&gt;
&lt;span class="go"&gt;2023-11-01 19:08:14.520 UTC [2908253] LOG:  invalid record length at 0/161DE70: expected at least 24, got 0&lt;/span&gt;
&lt;span class="go"&gt;2023-11-01 19:08:14.520 UTC [2908253] LOG:  redo done at 0/161DE38 system usage: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s&lt;/span&gt;
&lt;span class="go"&gt;2023-11-01 19:08:14.521 UTC [2908254] LOG:  checkpoint starting: end-of-recovery immediate wait&lt;/span&gt;
&lt;span class="go"&gt;2023-11-01 19:08:14.532 UTC [2908254] LOG:  checkpoint complete: wrote 35 buffers (0.2%); 0 WAL file(s) added, 0 removed, 0 recycled; write=0.001 s, sync=0.010 s, total=0.012 s; sync files=27, longest=0.003 s, average=0.001 s; distance=149 kB, estimate=149 kB; lsn=0/161DE70, redo lsn=0/161DE70&lt;/span&gt;
&lt;span class="go"&gt;2023-11-01 19:08:14.533 UTC [2906199] LOG:  database system is ready to accept connections&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This was the first place I got stuck. How on earth do I figure out
what methods to implement? I mean, it's clearly one or more of these
methods from the struct. But there are so many methods.&lt;/p&gt;
&lt;p&gt;I tried setting a breakpoint in &lt;code&gt;gdb&lt;/code&gt; on the process returned by
&lt;code&gt;SELECT pg_backend_pid()&lt;/code&gt; for a &lt;code&gt;psql&lt;/code&gt; session, but the breakpoint
never seemed to be hit for any of my methods.&lt;/p&gt;
&lt;p&gt;So I did the low-tech solution and opened a file, &lt;code&gt;/tmp/pgtam.log&lt;/code&gt;,
turned off buffering on it, and added a log to every method on the
&lt;code&gt;TableAmRoutine&lt;/code&gt; struct:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -12,9 +12,13 @@&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;const TableAmRoutine memam_methods;

&lt;span class="gi"&gt;+FILE* fd;&lt;/span&gt;
&lt;span class="gi"&gt;+#define DEBUG_FUNC() fprintf(fd, &amp;quot;in %s\n&amp;quot;, __func__);&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;static const TupleTableSlotOps* memam_slot_callbacks(
&lt;span class="w"&gt; &lt;/span&gt;  Relation relation
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return NULL;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -26,6 +30,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  ParallelTableScanDesc parallel_scan,
&lt;span class="w"&gt; &lt;/span&gt;  uint32 flags
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return NULL;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -37,9 +42,11 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  bool allow_sync,
&lt;span class="w"&gt; &lt;/span&gt;  bool allow_pagemode
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static void memam_endscan(TableScanDesc sscan) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static bool memam_getnextslot(
&lt;span class="gu"&gt;@@ -47,17 +54,21 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  ScanDirection direction,
&lt;span class="w"&gt; &lt;/span&gt;  TupleTableSlot *slot
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return false;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static IndexFetchTableData* memam_index_fetch_begin(Relation rel) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return NULL;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static void memam_index_fetch_reset(IndexFetchTableData *scan) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static void memam_index_fetch_end(IndexFetchTableData *scan) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static bool memam_index_fetch_tuple(
&lt;span class="gu"&gt;@@ -68,6 +79,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  bool *call_again,
&lt;span class="w"&gt; &lt;/span&gt;  bool *all_dead
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return false;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -78,6 +90,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  int options,
&lt;span class="w"&gt; &lt;/span&gt;  BulkInsertState bistate
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static void memam_tuple_insert_speculative(
&lt;span class="gu"&gt;@@ -87,6 +100,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  int options,
&lt;span class="w"&gt; &lt;/span&gt;  BulkInsertState bistate,
&lt;span class="w"&gt; &lt;/span&gt;  uint32 specToken) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static void memam_tuple_complete_speculative(
&lt;span class="gu"&gt;@@ -94,6 +108,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  TupleTableSlot *slot,
&lt;span class="w"&gt; &lt;/span&gt;  uint32 specToken,
&lt;span class="w"&gt; &lt;/span&gt;  bool succeeded) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static void memam_multi_insert(
&lt;span class="gu"&gt;@@ -104,6 +119,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  int options,
&lt;span class="w"&gt; &lt;/span&gt;  BulkInsertState bistate
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static TM_Result memam_tuple_delete(
&lt;span class="gu"&gt;@@ -117,6 +133,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  bool changingPart
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="w"&gt; &lt;/span&gt;  TM_Result result = {};
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return result;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -133,6 +150,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  TU_UpdateIndexes *update_indexes
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="w"&gt; &lt;/span&gt;  TM_Result result = {};
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return result;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -148,6 +166,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  TM_FailureData *tmfd)
&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt; &lt;/span&gt;  TM_Result result = {};
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return result;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -157,6 +176,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  Snapshot snapshot,
&lt;span class="w"&gt; &lt;/span&gt;  TupleTableSlot *slot
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return false;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -164,9 +184,11 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  TableScanDesc sscan,
&lt;span class="w"&gt; &lt;/span&gt;  ItemPointer tid
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static bool memam_tuple_tid_valid(TableScanDesc scan, ItemPointer tid) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return false;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -175,6 +197,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  TupleTableSlot *slot,
&lt;span class="w"&gt; &lt;/span&gt;  Snapshot snapshot
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return false;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -183,6 +206,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  TM_IndexDeleteOp *delstate
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="w"&gt; &lt;/span&gt;  TransactionId id = {};
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return id;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -193,17 +217,20 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  TransactionId *freezeXid,
&lt;span class="w"&gt; &lt;/span&gt;  MultiXactId *minmulti
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static void memam_relation_nontransactional_truncate(
&lt;span class="w"&gt; &lt;/span&gt;  Relation rel
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static void memam_relation_copy_data(
&lt;span class="w"&gt; &lt;/span&gt;  Relation rel,
&lt;span class="w"&gt; &lt;/span&gt;  const RelFileLocator *newrlocator
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static void memam_relation_copy_for_cluster(
&lt;span class="gu"&gt;@@ -218,6 +245,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  double *tups_vacuumed,
&lt;span class="w"&gt; &lt;/span&gt;  double *tups_recently_dead
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static void memam_vacuum_rel(
&lt;span class="gu"&gt;@@ -225,6 +253,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  VacuumParams *params,
&lt;span class="w"&gt; &lt;/span&gt;  BufferAccessStrategy bstrategy
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static bool memam_scan_analyze_next_block(
&lt;span class="gu"&gt;@@ -232,6 +261,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  BlockNumber blockno,
&lt;span class="w"&gt; &lt;/span&gt;  BufferAccessStrategy bstrategy
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return false;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -242,6 +272,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  double *deadrows,
&lt;span class="w"&gt; &lt;/span&gt;  TupleTableSlot *slot
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return false;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -258,6 +289,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  void *callback_state,
&lt;span class="w"&gt; &lt;/span&gt;  TableScanDesc scan
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return 0;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -268,14 +300,17 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  Snapshot snapshot,
&lt;span class="w"&gt; &lt;/span&gt;  ValidateIndexState *state
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static bool memam_relation_needs_toast_table(Relation rel) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return false;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static Oid memam_relation_toast_am(Relation rel) {
&lt;span class="w"&gt; &lt;/span&gt;  Oid oid = {};
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return oid;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -287,6 +322,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  int32 slicelength,
&lt;span class="w"&gt; &lt;/span&gt;  struct varlena *result
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static void memam_estimate_rel_size(
&lt;span class="gu"&gt;@@ -296,11 +332,13 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  double *tuples,
&lt;span class="w"&gt; &lt;/span&gt;  double *allvisfrac
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static bool memam_scan_sample_next_block(
&lt;span class="w"&gt; &lt;/span&gt;  TableScanDesc scan, SampleScanState *scanstate
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return false;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -309,6 +347,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  SampleScanState *scanstate,
&lt;span class="w"&gt; &lt;/span&gt;  TupleTableSlot *slot
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return false;
&lt;span class="w"&gt; &lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And then in the entrypoint, initialize the file for logging.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -369,5 +408,9 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;PG_FUNCTION_INFO_V1(mem_tableam_handler);

&lt;span class="w"&gt; &lt;/span&gt;Datum mem_tableam_handler(PG_FUNCTION_ARGS) {
&lt;span class="gi"&gt;+  fd = fopen(&amp;quot;/tmp/pgtam.log&amp;quot;, &amp;quot;a&amp;quot;);&lt;/span&gt;
&lt;span class="gi"&gt;+  setvbuf(fd, NULL, _IONBF, 0); // Prevent buffering&lt;/span&gt;
&lt;span class="gi"&gt;+  fprintf(fd, &amp;quot;\n\nmem_tableam handler loaded\n&amp;quot;);&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  PG_RETURN_POINTER(&amp;amp;memam_methods);
&lt;span class="w"&gt; &lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's give it a shot!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
&lt;span class="go"&gt;psql:test.sql:1: NOTICE:  drop cascades to table x&lt;/span&gt;
&lt;span class="go"&gt;DROP EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE TABLE&lt;/span&gt;
&lt;span class="go"&gt;psql:test.sql:6: server closed the connection unexpectedly&lt;/span&gt;
&lt;span class="go"&gt;        This probably means the server terminated abnormally&lt;/span&gt;
&lt;span class="go"&gt;        before or while processing the request.&lt;/span&gt;
&lt;span class="go"&gt;psql:test.sql:6: error: connection to server was lost&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And let's check our log file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pgtam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;


&lt;span class="n"&gt;mem_tableam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;


&lt;span class="n"&gt;mem_tableam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;
&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_relation_set_new_filelocator&lt;/span&gt;


&lt;span class="n"&gt;mem_tableam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;
&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_relation_needs_toast_table&lt;/span&gt;


&lt;span class="n"&gt;mem_tableam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;
&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_estimate_rel_size&lt;/span&gt;
&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_slot_callbacks&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we're getting somewhere!&lt;/p&gt;
&lt;p class="note"&gt;
  I later realized &lt;code&gt;elog()&lt;/code&gt; is the way most people log
  within Postgres/within extensions. I didn't know that when I was
  getting started though. This separate logging was a simple way to
  get the info out.
&lt;/p&gt;&lt;h4 id="&amp;lt;code&amp;gt;slot_callbacks&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;slot_callbacks&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;Since the request crashes and the last logged function is
&lt;code&gt;memam_slot_callbacks&lt;/code&gt;, it seems like that is where we should
concentrate. The &lt;a href="https://www.postgresql.org/docs/current/tableam.html"&gt;table access method
docs&lt;/a&gt; suggest
looking at the default &lt;code&gt;heap&lt;/code&gt; access method for inspiration.&lt;/p&gt;
&lt;p&gt;Its
&lt;a href="https://github.com/postgres/postgres/blob/849172ff4883d44168f96f39d3fde96d0aa34c99/src/backend/access/heap/heapam_handler.c#L67"&gt;version&lt;/a&gt;
of &lt;code&gt;slot_callbacks&lt;/code&gt; returns &lt;code&gt;&amp;amp;TTSOpsBufferHeapTuple&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlotOps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="nf"&gt;heapam_slot_callbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;TTSOpsBufferHeapTuple&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I have no idea what that means, but since it is defined in
&lt;a href="https://github.com/postgres/postgres/blob/849172ff4883d44168f96f39d3fde96d0aa34c99/src/backend/executor/execTuples.c#L1080"&gt;&lt;code&gt;src/backend/executor/execTuples.c&lt;/code&gt;&lt;/a&gt;
it doesn't seem to be tied to the &lt;code&gt;heap&lt;/code&gt; access method
implementation. Let's try it.&lt;/p&gt;
&lt;p class="note"&gt;
  While it works initially, I noticed later on that
  &lt;code&gt;TTSOpsBufferHeapTuple&lt;/code&gt; turns out not to be the right
  choice here. &lt;code&gt;TTSOpsVirtual&lt;/code&gt; seems to be the right
  implementation.
&lt;/p&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -19,7 +19,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  Relation relation
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="w"&gt; &lt;/span&gt;  DEBUG_FUNC();
&lt;span class="gd"&gt;-  return NULL;&lt;/span&gt;
&lt;span class="gi"&gt;+  return &amp;amp;TTSOpsVirtual;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static TableScanDesc memam_beginscan(
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Build and run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
&lt;span class="go"&gt;psql:test.sql:1: NOTICE:  drop cascades to table x&lt;/span&gt;
&lt;span class="go"&gt;DROP EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE TABLE&lt;/span&gt;
&lt;span class="go"&gt;psql:test.sql:6: server closed the connection unexpectedly&lt;/span&gt;
&lt;span class="go"&gt;        This probably means the server terminated abnormally&lt;/span&gt;
&lt;span class="go"&gt;        before or while processing the request.&lt;/span&gt;
&lt;span class="go"&gt;psql:test.sql:6: error: connection to server was lost&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It still crashes. But this time in &lt;code&gt;/tmp/pgtam.log&lt;/code&gt; we made it into a
new method!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pgtam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;


&lt;span class="n"&gt;mem_tableam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;


&lt;span class="n"&gt;mem_tableam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;
&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_relation_set_new_filelocator&lt;/span&gt;


&lt;span class="n"&gt;mem_tableam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;
&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_relation_needs_toast_table&lt;/span&gt;


&lt;span class="n"&gt;mem_tableam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;
&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_estimate_rel_size&lt;/span&gt;
&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_slot_callbacks&lt;/span&gt;
&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_beginscan&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id="&amp;lt;code&amp;gt;scan_begin&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;scan_begin&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;The function signature is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;TableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;heap_beginscan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nkeys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ScanKey&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ParallelTableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parallel_scan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;uint32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Since we just implemented stub versions of all the methods, we've been
returning &lt;code&gt;NULL&lt;/code&gt;. Since we're failing in this function, maybe we
should try returning something that isn't &lt;code&gt;NULL&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;By looking at the definition of &lt;code&gt;TableScanDesc&lt;/code&gt;, we can see it is a
pointer to the &lt;code&gt;TableScanDescData&lt;/code&gt; struct defined in
&lt;a href="https://github.com/postgres/postgres/blob/849172ff4883d44168f96f39d3fde96d0aa34c99/src/include/access/relscan.h#L52"&gt;&lt;code&gt;src/include/access/relscan.h&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let's &lt;code&gt;malloc&lt;/code&gt; a &lt;code&gt;TableScanDescData&lt;/code&gt;, free it in &lt;code&gt;endscan&lt;/code&gt;, and return
the &lt;code&gt;TableScanDescData&lt;/code&gt; instance in &lt;code&gt;beginscan&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -30,8 +30,12 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  ParallelTableScanDesc parallel_scan,
&lt;span class="w"&gt; &lt;/span&gt;  uint32 flags
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  TableScanDescData* scan = {};&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  DEBUG_FUNC();
&lt;span class="gd"&gt;-  return NULL;&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  scan = (TableScanDescData*)malloc(sizeof(TableScanDescData));&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  return (TableScanDesc)scan;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static void memam_rescan(
&lt;span class="gu"&gt;@@ -87,6 +87,7 @@&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;static void memam_endscan(TableScanDesc sscan) {
&lt;span class="w"&gt; &lt;/span&gt;  DEBUG_FUNC();
&lt;span class="gi"&gt;+  free(sscan);&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Build and run (you can do it on your own). No difference.&lt;/p&gt;
&lt;p&gt;I got stuck for a while here too. Clearly something must be filled out
in this struct but it could be anything. Through trial and error I
realized the one field that must be filled out is &lt;code&gt;scan-&amp;gt;rs_rd&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -34,6 +34,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  DEBUG_FUNC();

&lt;span class="w"&gt; &lt;/span&gt;  scan = (TableScanDescData*)malloc(sizeof(TableScanDescData));
&lt;span class="gi"&gt;+  scan-&amp;gt;rs_rd = relation;&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;  return (TableScanDesc)scan;
&lt;span class="w"&gt; &lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We build and run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
$&lt;span class="w"&gt; &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
psql:test.sql:1:&lt;span class="w"&gt; &lt;/span&gt;NOTICE:&lt;span class="w"&gt;  &lt;/span&gt;drop&lt;span class="w"&gt; &lt;/span&gt;cascades&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;table&lt;span class="w"&gt; &lt;/span&gt;x
DROP&lt;span class="w"&gt; &lt;/span&gt;EXTENSION
CREATE&lt;span class="w"&gt; &lt;/span&gt;EXTENSION
CREATE&lt;span class="w"&gt; &lt;/span&gt;TABLE
&lt;span class="w"&gt; &lt;/span&gt;a
---
&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rows&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And it works! It doesn't return anything but that's correct. There's
nothing to return.&lt;/p&gt;
&lt;p&gt;So what if we actually want to return something? Let's check our logs
in &lt;code&gt;/tmp/pgtam.log&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pgtam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;


&lt;span class="n"&gt;mem_tableam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;


&lt;span class="n"&gt;mem_tableam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;
&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_relation_set_new_filelocator&lt;/span&gt;


&lt;span class="n"&gt;mem_tableam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;
&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_relation_needs_toast_table&lt;/span&gt;


&lt;span class="n"&gt;mem_tableam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;
&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_estimate_rel_size&lt;/span&gt;
&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_slot_callbacks&lt;/span&gt;
&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_beginscan&lt;/span&gt;
&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_getnextslot&lt;/span&gt;
&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_endscan&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ok, I'm getting the gist of the API. A full table scan (which this is,
because there are no indexes at play) starts with an initialization
for a slot, then the scan begins, then &lt;code&gt;getnextslot&lt;/code&gt; is called for
each row, and then &lt;code&gt;endscan&lt;/code&gt; is called to allow for cleanup.&lt;/p&gt;
&lt;p&gt;So let's try returning a row in &lt;code&gt;getnextslot&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id="&amp;lt;code&amp;gt;getnextslot&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;getnextslot&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;The &lt;code&gt;getnextslot&lt;/code&gt; signature is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_getnextslot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sscan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ScanDirection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So the &lt;code&gt;sscan&lt;/code&gt; should be what we returned from &lt;code&gt;beginscan&lt;/code&gt; and the
&lt;a href="https://github.com/postgres/postgres/blob/849172ff4883d44168f96f39d3fde96d0aa34c99/src/include/access/tableam.h#L341"&gt;interface
docs&lt;/a&gt;
say the current row gets stored in &lt;code&gt;slot&lt;/code&gt;.&lt;/p&gt;
&lt;p class="note"&gt;
  The return value seems to indicate whether or not we've reached the
  end of the scan. However, the scan will still end even if you
  &lt;code&gt;return true&lt;/code&gt; if the &lt;code&gt;slot&lt;/code&gt; is not filled out correctly. If the
  &lt;code&gt;slot&lt;/code&gt; is filled out correctly and you unconditionally &lt;code&gt;return
  true&lt;/code&gt;, you will crash the process.
&lt;/p&gt;&lt;p&gt;Let's take a look at the
&lt;a href="https://github.com/postgres/postgres/blob/849172ff4883d44168f96f39d3fde96d0aa34c99/src/include/executor/tuptable.h#L114"&gt;definition&lt;/a&gt;
of &lt;code&gt;TupleTableSlot&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;TupleTableSlot&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;NodeTag&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cp"&gt;#define FIELDNO_TUPLETABLESLOT_FLAGS 1&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;uint16&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;tts_flags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="cm"&gt;/* Boolean states */&lt;/span&gt;
&lt;span class="cp"&gt;#define FIELDNO_TUPLETABLESLOT_NVALID 2&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AttrNumber&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;tts_nvalid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="cm"&gt;/* # of valid values in tts_values */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlotOps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tts_ops&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* implementation of slot */&lt;/span&gt;
&lt;span class="cp"&gt;#define FIELDNO_TUPLETABLESLOT_TUPLEDESCRIPTOR 4&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;TupleDesc&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;tts_tupleDescriptor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* slot's tuple descriptor */&lt;/span&gt;
&lt;span class="cp"&gt;#define FIELDNO_TUPLETABLESLOT_VALUES 5&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Datum&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tts_values&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="cm"&gt;/* current per-attribute values */&lt;/span&gt;
&lt;span class="cp"&gt;#define FIELDNO_TUPLETABLESLOT_ISNULL 6&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tts_isnull&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="cm"&gt;/* current per-attribute isnull flags */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MemoryContext&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tts_mcxt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="cm"&gt;/* slot itself is in this context */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;ItemPointerData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tts_tid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* stored tuple's tid */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Oid&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;tts_tableOid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="cm"&gt;/* table oid of tuple */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;tts_values&lt;/code&gt; is an array of &lt;code&gt;Datum&lt;/code&gt; (which is a Postgres value). So
that sounds like the actual values of the row. The &lt;code&gt;tts_isnull&lt;/code&gt; field
also looks important since that seems to be whether each value in the
row is null or not. And &lt;code&gt;tts_nvalid&lt;/code&gt; sounds important too since
presumably it's the length of the &lt;code&gt;tts_isnull&lt;/code&gt; and &lt;code&gt;tts_values&lt;/code&gt;
arrays.&lt;/p&gt;
&lt;p&gt;The rest of it may or may not be important. Let's try filling out
these three fields though and see what happens.&lt;/p&gt;
&lt;h4 id="datum"&gt;Datum&lt;/h4&gt;&lt;p&gt;Back in the &lt;a href="https://www.postgresql.org/docs/current/xfunc-c.html"&gt;Postgres C extension
documentation&lt;/a&gt;,
we can see some simple examples of converting between C types and
Postgres's Datum type.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;Datum&lt;/span&gt;
&lt;span class="nf"&gt;add_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PG_FUNCTION_ARGS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;int32&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PG_GETARG_INT32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;PG_RETURN_INT32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If we look at the definition of &lt;code&gt;PG_RETURN_INT32&lt;/code&gt; in
&lt;a href="https://github.com/postgres/postgres/blob/849172ff4883d44168f96f39d3fde96d0aa34c99/src/include/fmgr.h#L354"&gt;&lt;code&gt;src/include/fmgr.h&lt;/code&gt;&lt;/a&gt;,
we see:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;#define PG_RETURN_INT32(x)   return Int32GetDatum(x)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So &lt;code&gt;Int32GetDatum()&lt;/code&gt; is the function we'll use to set a &lt;code&gt;Datum&lt;/code&gt; for a
cell in a row.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -54,13 +54,26 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  DEBUG_FUNC();
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gi"&gt;+static bool done = false;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;static bool memam_getnextslot(
&lt;span class="w"&gt; &lt;/span&gt;  TableScanDesc sscan,
&lt;span class="w"&gt; &lt;/span&gt;  ScanDirection direction,
&lt;span class="w"&gt; &lt;/span&gt;  TupleTableSlot *slot
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="w"&gt; &lt;/span&gt;  DEBUG_FUNC();
&lt;span class="gd"&gt;-  return false;&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  if (done) {&lt;/span&gt;
&lt;span class="gi"&gt;+    return false;&lt;/span&gt;
&lt;span class="gi"&gt;+  }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  slot-&amp;gt;tts_nvalid = 1;&lt;/span&gt;
&lt;span class="gi"&gt;+  slot-&amp;gt;tts_values = (Datum*)malloc(sizeof(Datum) * slot-&amp;gt;tts_nvalid);&lt;/span&gt;
&lt;span class="gi"&gt;+  slot-&amp;gt;tts_values[0] = Int32GetDatum(314 /* Some unique-looking value */);&lt;/span&gt;
&lt;span class="gi"&gt;+  slot-&amp;gt;tts_isnull = (bool*)malloc(sizeof(bool) * slot-&amp;gt;tts_nvalid);&lt;/span&gt;
&lt;span class="gi"&gt;+  slot-&amp;gt;tts_isnull[0] = false;&lt;/span&gt;
&lt;span class="gi"&gt;+  done = true;&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  return true;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static IndexFetchTableData* memam_index_fetch_begin(Relation rel) {
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The goal is that we return a single row and then exit the scan. It
will have one 32-bit integer cell (remember we created the table
&lt;code&gt;CREATE TABLE x (a INT)&lt;/code&gt;; &lt;code&gt;INT&lt;/code&gt; is shorthand for &lt;code&gt;INT4&lt;/code&gt; which is a
32-bit integer) that will have the value &lt;code&gt;314&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;But if we build and run this, we get no rows.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
&lt;span class="go"&gt;psql:test.sql:1: NOTICE:  drop cascades to table x&lt;/span&gt;
&lt;span class="go"&gt;DROP EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE TABLE&lt;/span&gt;
&lt;span class="go"&gt; a&lt;/span&gt;
&lt;span class="go"&gt;---&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(0 rows)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I got stuck for a while here. Plugging my &lt;code&gt;getnextslot&lt;/code&gt; code into
ChatGPT helped. One thing it gave me to try was calling
&lt;code&gt;ExecStoreVirtualTuple&lt;/code&gt; on the &lt;code&gt;slot&lt;/code&gt;. I noticed that the built-in
&lt;code&gt;heap&lt;/code&gt; access method &lt;a href="https://github.com/postgres/postgres/blob/849172ff4883d44168f96f39d3fde96d0aa34c99/src/backend/access/heap/heapam.c#L1159"&gt;also called a function like
this&lt;/a&gt;
in &lt;code&gt;getnextslot&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;And I realized that &lt;code&gt;tts_nvalid&lt;/code&gt; is already set up and the memory for
&lt;code&gt;tts_values&lt;/code&gt; and &lt;code&gt;tts_isnull&lt;/code&gt; is already allocated. So the code became
a little simpler.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -66,11 +66,9 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;    return false;
&lt;span class="w"&gt; &lt;/span&gt;  }

&lt;span class="gd"&gt;-  slot-&amp;gt;tts_nvalid = 1;&lt;/span&gt;
&lt;span class="gd"&gt;-  slot-&amp;gt;tts_values = (Datum*)malloc(sizeof(Datum) * slot-&amp;gt;tts_nvalid);&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  slot-&amp;gt;tts_values[0] = Int32GetDatum(314 /* Some unique-looking value */);
&lt;span class="gd"&gt;-  slot-&amp;gt;tts_isnull = (bool*)malloc(sizeof(bool) * slot-&amp;gt;tts_nvalid);&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  slot-&amp;gt;tts_isnull[0] = false;
&lt;span class="gi"&gt;+  ExecStoreVirtualTuple(slot);&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  done = true;

&lt;span class="w"&gt; &lt;/span&gt;  return true;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Build and run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
&lt;span class="go"&gt;psql:test.sql:1: NOTICE:  drop cascades to table x&lt;/span&gt;
&lt;span class="go"&gt;DROP EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE TABLE&lt;/span&gt;
&lt;span class="go"&gt;  a&lt;/span&gt;
&lt;span class="go"&gt;-----&lt;/span&gt;
&lt;span class="go"&gt; 314&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(1 row)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Fantastic!&lt;/p&gt;
&lt;h3 id="creating-a-table"&gt;Creating a table&lt;/h3&gt;&lt;p&gt;Now that we've proven we can return random data, let's set up
infrastructure for storing tables in memory.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -15,6 +15,41 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;FILE* fd;
&lt;span class="w"&gt; &lt;/span&gt;#define DEBUG_FUNC() fprintf(fd, &amp;quot;in %s\n&amp;quot;, __func__);

&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+struct Column {&lt;/span&gt;
&lt;span class="gi"&gt;+  int value;&lt;/span&gt;
&lt;span class="gi"&gt;+};&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+struct Row {&lt;/span&gt;
&lt;span class="gi"&gt;+  struct Column* columns;&lt;/span&gt;
&lt;span class="gi"&gt;+  size_t ncolumns;&lt;/span&gt;
&lt;span class="gi"&gt;+};&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+#define MAX_ROWS 100&lt;/span&gt;
&lt;span class="gi"&gt;+struct Table {&lt;/span&gt;
&lt;span class="gi"&gt;+  char* name;&lt;/span&gt;
&lt;span class="gi"&gt;+  struct Row* rows;&lt;/span&gt;
&lt;span class="gi"&gt;+  size_t nrows;&lt;/span&gt;
&lt;span class="gi"&gt;+};&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+#define MAX_TABLES 100&lt;/span&gt;
&lt;span class="gi"&gt;+struct Database {&lt;/span&gt;
&lt;span class="gi"&gt;+  struct Table* tables;&lt;/span&gt;
&lt;span class="gi"&gt;+  size_t ntables;&lt;/span&gt;
&lt;span class="gi"&gt;+};&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+struct Database* database;&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+static void get_table(struct Table** table, Relation relation) {&lt;/span&gt;
&lt;span class="gi"&gt;+  char* this_name = NameStr(relation-&amp;gt;rd_rel-&amp;gt;relname);&lt;/span&gt;
&lt;span class="gi"&gt;+  for (size_t i = 0; i &amp;lt; database-&amp;gt;ntables; i++) {&lt;/span&gt;
&lt;span class="gi"&gt;+    if (strcmp(database-&amp;gt;tables[i].name, this_name) == 0) {&lt;/span&gt;
&lt;span class="gi"&gt;+      *table = &amp;amp;database-&amp;gt;tables[i];&lt;/span&gt;
&lt;span class="gi"&gt;+      return;&lt;/span&gt;
&lt;span class="gi"&gt;+    }&lt;/span&gt;
&lt;span class="gi"&gt;+  }&lt;/span&gt;
&lt;span class="gi"&gt;+}&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;static const TupleTableSlotOps* memam_slot_callbacks(
&lt;span class="w"&gt; &lt;/span&gt;  Relation relation
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Based on what we logged in &lt;code&gt;/tmp/pgtam.log&lt;/code&gt; it seems like
&lt;code&gt;memam_relation_set_new_filelocator&lt;/code&gt; is called when a new table is
created. So let's handle adding a new table there.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -233,7 +268,16 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  TransactionId *freezeXid,
&lt;span class="w"&gt; &lt;/span&gt;  MultiXactId *minmulti
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  struct Table table = {};&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  DEBUG_FUNC();
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  table.name = strdup(NameStr(rel-&amp;gt;rd_rel-&amp;gt;relname));&lt;/span&gt;
&lt;span class="gi"&gt;+  fprintf(fd, &amp;quot;Created table: [%s]\n&amp;quot;, table.name);&lt;/span&gt;
&lt;span class="gi"&gt;+  table.rows = (struct Row*)malloc(sizeof(struct Row) * MAX_ROWS);&lt;/span&gt;
&lt;span class="gi"&gt;+  table.nrows = 0;&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  database-&amp;gt;tables[database-&amp;gt;ntables] = table;&lt;/span&gt;
&lt;span class="gi"&gt;+  database-&amp;gt;ntables++;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static void memam_relation_nontransactional_truncate(
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Finally, we'll initialize the in-memory &lt;code&gt;Database*&lt;/code&gt; when the handler is
loaded.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -428,5 +472,11 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  setvbuf(fd, NULL, _IONBF, 0); // Prevent buffering
&lt;span class="w"&gt; &lt;/span&gt;  fprintf(fd, &amp;quot;\n\nmem_tableam handler loaded\n&amp;quot;);

&lt;span class="gi"&gt;+  if (database == NULL) {&lt;/span&gt;
&lt;span class="gi"&gt;+    database = (struct Database*)malloc(sizeof(struct Database));&lt;/span&gt;
&lt;span class="gi"&gt;+    database-&amp;gt;ntables = 0;&lt;/span&gt;
&lt;span class="gi"&gt;+    database-&amp;gt;tables = (struct Table*)malloc(sizeof(struct Table) * MAX_TABLES);&lt;/span&gt;
&lt;span class="gi"&gt;+  }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  PG_RETURN_POINTER(&amp;amp;memam_methods);
&lt;span class="w"&gt; &lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If we build and run, we won't notice anything new.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
&lt;span class="go"&gt;psql:test.sql:1: NOTICE:  drop cascades to table x&lt;/span&gt;
&lt;span class="go"&gt;DROP EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE TABLE&lt;/span&gt;
&lt;span class="go"&gt;  a&lt;/span&gt;
&lt;span class="go"&gt;-----&lt;/span&gt;
&lt;span class="go"&gt; 314&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(1 row)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But we should see a message in &lt;code&gt;/tmp/pgtam.log&lt;/code&gt; about the new table
being created.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;/tmp/pgtam.log


&lt;span class="go"&gt;mem_tableam handler loaded&lt;/span&gt;


&lt;span class="go"&gt;mem_tableam handler loaded&lt;/span&gt;
&lt;span class="go"&gt;in memam_relation_set_new_filelocator&lt;/span&gt;
&lt;span class="go"&gt;Created table: [x]&lt;/span&gt;


&lt;span class="go"&gt;mem_tableam handler loaded&lt;/span&gt;
&lt;span class="go"&gt;in memam_relation_needs_toast_table&lt;/span&gt;


&lt;span class="go"&gt;mem_tableam handler loaded&lt;/span&gt;
&lt;span class="go"&gt;in memam_estimate_rel_size&lt;/span&gt;
&lt;span class="go"&gt;in memam_slot_callbacks&lt;/span&gt;
&lt;span class="go"&gt;in memam_beginscan&lt;/span&gt;
&lt;span class="go"&gt;in memam_getnextslot&lt;/span&gt;
&lt;span class="go"&gt;in memam_getnextslot&lt;/span&gt;
&lt;span class="go"&gt;in memam_endscan&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And there it is! Creation looks good.&lt;/p&gt;
&lt;h3 id="inserting-rows"&gt;Inserting rows&lt;/h3&gt;&lt;p&gt;Let's add &lt;code&gt;INSERT INTO x VALUES (23), (101);&lt;/code&gt; to &lt;code&gt;test.sql&lt;/code&gt; and run
the SQL script.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
&lt;span class="go"&gt;psql:test.sql:1: NOTICE:  drop cascades to table x&lt;/span&gt;
&lt;span class="go"&gt;DROP EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE TABLE&lt;/span&gt;
&lt;span class="go"&gt;INSERT 0 2&lt;/span&gt;
&lt;span class="go"&gt;  a&lt;/span&gt;
&lt;span class="go"&gt;-----&lt;/span&gt;
&lt;span class="go"&gt; 314&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(1 row)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And let's check the log to see what method is called when we try to
&lt;code&gt;INSERT&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;/tmp/pgtam.log


&lt;span class="go"&gt;mem_tableam handler loaded&lt;/span&gt;


&lt;span class="go"&gt;mem_tableam handler loaded&lt;/span&gt;
&lt;span class="go"&gt;in memam_relation_set_new_filelocator&lt;/span&gt;
&lt;span class="go"&gt;Created table: [x]&lt;/span&gt;


&lt;span class="go"&gt;mem_tableam handler loaded&lt;/span&gt;
&lt;span class="go"&gt;in memam_relation_needs_toast_table&lt;/span&gt;


&lt;span class="go"&gt;mem_tableam handler loaded&lt;/span&gt;
&lt;span class="go"&gt;in memam_slot_callbacks&lt;/span&gt;
&lt;span class="go"&gt;in memam_tuple_insert&lt;/span&gt;
&lt;span class="go"&gt;in memam_tuple_insert&lt;/span&gt;
&lt;span class="go"&gt;in memam_estimate_rel_size&lt;/span&gt;
&lt;span class="go"&gt;in memam_slot_callbacks&lt;/span&gt;
&lt;span class="go"&gt;in memam_beginscan&lt;/span&gt;
&lt;span class="go"&gt;in memam_getnextslot&lt;/span&gt;
&lt;span class="go"&gt;in memam_getnextslot&lt;/span&gt;
&lt;span class="go"&gt;in memam_endscan&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;tuple_insert&lt;/code&gt; seems to be the method! Looks like it gets called once
for each row to insert. Perfect.&lt;/p&gt;
&lt;p&gt;The signature for &lt;code&gt;tuple_insert&lt;/code&gt; is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_tuple_insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;CommandId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;BulkInsertState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bistate&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We can get the table name from &lt;code&gt;relation&lt;/code&gt;, and instead of writing to
&lt;code&gt;slot&lt;/code&gt; we can read from &lt;code&gt;slot-&amp;gt;tts_values&lt;/code&gt; instead.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -141,7 +141,38 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  int options,
&lt;span class="w"&gt; &lt;/span&gt;  BulkInsertState bistate
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  TupleDesc desc = RelationGetDescr(relation);&lt;/span&gt;
&lt;span class="gi"&gt;+  struct Table* table = NULL;&lt;/span&gt;
&lt;span class="gi"&gt;+  struct Column column = {};&lt;/span&gt;
&lt;span class="gi"&gt;+  struct Row row = {};&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  DEBUG_FUNC();
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  get_table(&amp;amp;table, relation);&lt;/span&gt;
&lt;span class="gi"&gt;+  if (table == NULL) {&lt;/span&gt;
&lt;span class="gi"&gt;+    elog(ERROR, &amp;quot;table not found&amp;quot;);&lt;/span&gt;
&lt;span class="gi"&gt;+    return;&lt;/span&gt;
&lt;span class="gi"&gt;+  }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  if (table-&amp;gt;nrows == MAX_ROWS) {&lt;/span&gt;
&lt;span class="gi"&gt;+    elog(ERROR, &amp;quot;cannot insert more rows&amp;quot;);&lt;/span&gt;
&lt;span class="gi"&gt;+    return;&lt;/span&gt;
&lt;span class="gi"&gt;+  }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  row.ncolumns = desc-&amp;gt;natts;&lt;/span&gt;
&lt;span class="gi"&gt;+  Assert(slot-&amp;gt;tts_nvalid == row.ncolumns);&lt;/span&gt;
&lt;span class="gi"&gt;+  Assert(row.ncolumns &amp;gt; 0);&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  row.columns = (struct Column*)malloc(sizeof(struct Column) * row.ncolumns);&lt;/span&gt;
&lt;span class="gi"&gt;+  for (size_t i = 0; i &amp;lt; row.ncolumns; i++) {&lt;/span&gt;
&lt;span class="gi"&gt;+    Assert(desc-&amp;gt;attrs[i].atttypid == INT4OID);&lt;/span&gt;
&lt;span class="gi"&gt;+    column.value = DatumGetInt32(slot-&amp;gt;tts_values[i]);&lt;/span&gt;
&lt;span class="gi"&gt;+    row.columns[i] = column;&lt;/span&gt;
&lt;span class="gi"&gt;+    fprintf(fd, &amp;quot;Got value: %d\n&amp;quot;, column.value);&lt;/span&gt;
&lt;span class="gi"&gt;+  }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  table-&amp;gt;rows[table-&amp;gt;nrows] = row;&lt;/span&gt;
&lt;span class="gi"&gt;+  table-&amp;gt;nrows++;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static void memam_tuple_insert_speculative(
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Build and run and again we won't notice anything new.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
&lt;span class="go"&gt;psql:test.sql:1: NOTICE:  drop cascades to table x&lt;/span&gt;
&lt;span class="go"&gt;DROP EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE TABLE&lt;/span&gt;
&lt;span class="go"&gt;INSERT 0 2&lt;/span&gt;
&lt;span class="go"&gt;  a&lt;/span&gt;
&lt;span class="go"&gt;-----&lt;/span&gt;
&lt;span class="go"&gt; 314&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(1 row)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But if we check the logs, we should see the two column values we
inserted, one for each row.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;/tmp/pgtam.log


&lt;span class="go"&gt;mem_tableam handler loaded&lt;/span&gt;


&lt;span class="go"&gt;mem_tableam handler loaded&lt;/span&gt;
&lt;span class="go"&gt;in memam_relation_set_new_filelocator&lt;/span&gt;
&lt;span class="go"&gt;Created table: [x]&lt;/span&gt;


&lt;span class="go"&gt;mem_tableam handler loaded&lt;/span&gt;
&lt;span class="go"&gt;in memam_relation_needs_toast_table&lt;/span&gt;


&lt;span class="go"&gt;mem_tableam handler loaded&lt;/span&gt;
&lt;span class="go"&gt;in memam_slot_callbacks&lt;/span&gt;
&lt;span class="go"&gt;in memam_tuple_insert&lt;/span&gt;
&lt;span class="go"&gt;Got value: 23&lt;/span&gt;
&lt;span class="go"&gt;in memam_tuple_insert&lt;/span&gt;
&lt;span class="go"&gt;Got value: 101&lt;/span&gt;
&lt;span class="go"&gt;in memam_estimate_rel_size&lt;/span&gt;
&lt;span class="go"&gt;in memam_slot_callbacks&lt;/span&gt;
&lt;span class="go"&gt;in memam_beginscan&lt;/span&gt;
&lt;span class="go"&gt;in memam_getnextslot&lt;/span&gt;
&lt;span class="go"&gt;in memam_getnextslot&lt;/span&gt;
&lt;span class="go"&gt;in memam_endscan&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Woohoo!&lt;/p&gt;
&lt;h3 id="un-hardcoding-the-scan"&gt;Un-hardcoding the scan&lt;/h3&gt;&lt;p&gt;The final thing we need to do is drop the hardcoded &lt;code&gt;314&lt;/code&gt; we returned
from &lt;code&gt;getnextslot&lt;/code&gt; and instead we need to look up the current table
and return rows from it. This also means we need to keep track of
which row we're on. So &lt;code&gt;beginscan&lt;/code&gt; will also need to change slightly.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -57,6 +56,14 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return &amp;amp;TTSOpsVirtual;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+struct MemScanDesc {&lt;/span&gt;
&lt;span class="gi"&gt;+  TableScanDescData rs_base; // Base class from access/relscan.h.&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  // Custom data.&lt;/span&gt;
&lt;span class="gi"&gt;+  uint32 cursor;&lt;/span&gt;
&lt;span class="gi"&gt;+};&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;static TableScanDesc memam_beginscan(
&lt;span class="w"&gt; &lt;/span&gt;  Relation relation,
&lt;span class="w"&gt; &lt;/span&gt;  Snapshot snapshot,
&lt;span class="gu"&gt;@@ -65,11 +72,13 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  ParallelTableScanDesc parallel_scan,
&lt;span class="w"&gt; &lt;/span&gt;  uint32 flags
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gd"&gt;-  TableScanDescData* scan = {};&lt;/span&gt;
&lt;span class="gd"&gt;-  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="gi"&gt;+  struct MemScanDesc* scan;&lt;/span&gt;

&lt;span class="gd"&gt;-  scan = (TableScanDescData*)malloc(sizeof(TableScanDescData));&lt;/span&gt;
&lt;span class="gd"&gt;-  scan-&amp;gt;rs_rd = relation;&lt;/span&gt;
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  scan = (struct MemScanDesc*)malloc(sizeof(struct MemScanDesc));&lt;/span&gt;
&lt;span class="gi"&gt;+  scan-&amp;gt;rs_base.rs_rd = relation;&lt;/span&gt;
&lt;span class="gi"&gt;+  scan-&amp;gt;cursor = 0;&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;  return (TableScanDesc)scan;
&lt;span class="w"&gt; &lt;/span&gt;}
&lt;span class="gu"&gt;@@ -89,23 +97,26 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  DEBUG_FUNC();
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gd"&gt;-static bool done = false;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;static bool memam_getnextslot(
&lt;span class="w"&gt; &lt;/span&gt;  TableScanDesc sscan,
&lt;span class="w"&gt; &lt;/span&gt;  ScanDirection direction,
&lt;span class="w"&gt; &lt;/span&gt;  TupleTableSlot *slot
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  struct MemScanDesc* mscan = (struct MemScanDesc*)sscan;&lt;/span&gt;
&lt;span class="gi"&gt;+  struct Table* table = NULL;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  DEBUG_FUNC();

&lt;span class="gd"&gt;-  if (done) {&lt;/span&gt;
&lt;span class="gi"&gt;+  ExecClearTuple(slot);&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  get_table(&amp;amp;table, mscan-&amp;gt;rs_base.rs_rd);&lt;/span&gt;
&lt;span class="gi"&gt;+  if (table == NULL || mscan-&amp;gt;cursor == table-&amp;gt;nrows) {&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;    return false;
&lt;span class="w"&gt; &lt;/span&gt;  }

&lt;span class="gd"&gt;-  slot-&amp;gt;tts_values[0] = Int32GetDatum(314 /* Some unique-looking value */);&lt;/span&gt;
&lt;span class="gi"&gt;+  slot-&amp;gt;tts_values[0] = Int32GetDatum(table-&amp;gt;rows[mscan-&amp;gt;cursor].columns[0].value);&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  slot-&amp;gt;tts_isnull[0] = false;
&lt;span class="w"&gt; &lt;/span&gt;  ExecStoreVirtualTuple(slot);
&lt;span class="gd"&gt;-  done = true;&lt;/span&gt;
&lt;span class="gd"&gt;-&lt;/span&gt;
&lt;span class="gi"&gt;+  mscan-&amp;gt;cursor++;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return true;
&lt;span class="w"&gt; &lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's try it out.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
&lt;span class="go"&gt;psql:test.sql:1: NOTICE:  drop cascades to table x&lt;/span&gt;
&lt;span class="go"&gt;DROP EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE TABLE&lt;/span&gt;
&lt;span class="go"&gt;INSERT 0 2&lt;/span&gt;
&lt;span class="go"&gt;  a&lt;/span&gt;
&lt;span class="go"&gt;-----&lt;/span&gt;
&lt;span class="go"&gt;  23&lt;/span&gt;
&lt;span class="go"&gt; 101&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(2 rows)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And there we have it. :)&lt;/p&gt;
&lt;h3 id="awesome-sql-power"&gt;Awesome SQL power&lt;/h3&gt;&lt;p&gt;So we tried one table and we tried a &lt;code&gt;SELECT&lt;/code&gt; without anything else.&lt;/p&gt;
&lt;p&gt;What happens if we use more of SQL? Let's create another table
and try some more complex queries. Edit &lt;code&gt;test.sql&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;DROP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;EXTENSION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;EXISTS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pgtam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;EXTENSION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pgtam&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INTO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;VALUES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Run it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
&lt;span class="go"&gt;psql:test.sql:1: NOTICE:  drop cascades to 2 other objects&lt;/span&gt;
&lt;span class="go"&gt;DETAIL:  drop cascades to table x&lt;/span&gt;
&lt;span class="go"&gt;drop cascades to table y&lt;/span&gt;
&lt;span class="go"&gt;DROP EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE TABLE&lt;/span&gt;
&lt;span class="go"&gt;CREATE TABLE&lt;/span&gt;
&lt;span class="go"&gt;INSERT 0 2&lt;/span&gt;
&lt;span class="go"&gt;  a&lt;/span&gt;
&lt;span class="go"&gt;-----&lt;/span&gt;
&lt;span class="go"&gt;  23&lt;/span&gt;
&lt;span class="go"&gt; 101&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(2 rows)&lt;/span&gt;

&lt;span class="go"&gt; ?column?&lt;/span&gt;
&lt;span class="go"&gt;----------&lt;/span&gt;
&lt;span class="go"&gt;      123&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(1 row)&lt;/span&gt;

&lt;span class="go"&gt;  a  | count&lt;/span&gt;
&lt;span class="go"&gt;-----+-------&lt;/span&gt;
&lt;span class="go"&gt;  23 |     1&lt;/span&gt;
&lt;span class="go"&gt; 101 |     1&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(2 rows)&lt;/span&gt;

&lt;span class="go"&gt; b&lt;/span&gt;
&lt;span class="go"&gt;---&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(0 rows)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Pretty sweet!&lt;/p&gt;
&lt;h3 id="next-steps"&gt;Next steps&lt;/h3&gt;&lt;p&gt;It would be neat to build a storage engine that reads from and writes
to a CSV a la MySQL's CSV storage engine. Or a storage engine that
uses RocksDB.&lt;/p&gt;
&lt;p&gt;It would also be good to figure out how indexes work, how deletions
work, how updates and DDL beyond &lt;code&gt;CREATE&lt;/code&gt; works.&lt;/p&gt;
&lt;p&gt;And I should probably contribute some of this to the &lt;a href="https://www.postgresql.org/docs/current/tableam.html"&gt;table access
method&lt;/a&gt; docs
which are pretty sparse at the moment.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I've been working this week to understand Postgres Table Access Methods for alternative storage engines.&lt;br /&gt;&lt;br /&gt;Especially challenging because the documentation is pretty sparse and few minimal implementations exist.&lt;br /&gt;&lt;br /&gt;I wrote up my approach!&lt;a href="https://t.co/LQGglRkev5"&gt;https://t.co/LQGglRkev5&lt;/a&gt; &lt;a href="https://t.co/v0MeOu4Hbr"&gt;pic.twitter.com/v0MeOu4Hbr&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1719873793693221157?ref_src=twsrc%5Etfw"&gt;November 2, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Wed, 01 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2023-11-01-postgres-table-access-methods.html</guid></item><item><title>Ruthless prioritization</title><link>https://swaits.com/prioritizing-with-eisenhower-matrix/</link><description>&lt;p&gt;I feel overwhelmed these days. My TODO list has spiraled out of control and I
feel immobilized. I need to get myself organized, and I need to ruthlessly
prioritize. &lt;a href="https://www.linkedin.com/in/michael-terkowitz-1ba89a3/" rel="external"&gt;Someone smart at
Amazon&lt;/a&gt; once said that
you'll have 100 things you &lt;em&gt;need&lt;/em&gt; or &lt;em&gt;want&lt;/em&gt; to do; but you can only do &lt;em&gt;one or
two&lt;/em&gt; of those and need to &lt;em&gt;throw the rest away&lt;/em&gt;. That's tough, but important!&lt;/p&gt;
&lt;p&gt;Here I describe an approach I was using in the recent past, and an approach I'm
going to move to today.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Contents:&lt;/strong&gt;&lt;/p&gt;
&lt;!--toc:start--&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://swaits.com/prioritizing-with-eisenhower-matrix/#my-most-recent-pen-and-paper-productivity-system"&gt;My most recent pen and paper productivity system&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/prioritizing-with-eisenhower-matrix/#my-not-so-new-new-pen-and-paper-system"&gt;My not-so-new, new pen and paper system&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://swaits.com/prioritizing-with-eisenhower-matrix/#using-the-system"&gt;Using the system&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/prioritizing-with-eisenhower-matrix/#download-the-pdf-or-copy-the-google-sheet-template"&gt;Download the PDF or copy the Google Sheet template&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;!--toc:end--&gt;
&lt;h3 id="my-most-recent-pen-and-paper-productivity-system"&gt;My most recent pen and paper productivity system&lt;/h3&gt;
&lt;p&gt;I like productivity tools. And I always find myself coming back to pen and
paper stuff. Writing things on paper is very satisfying. Something about the
physicality of it just feels nice.&lt;/p&gt;
&lt;p&gt;For some time, I used this &lt;a href="https://inkandvolt.com/products/ink-volt-dashboard-deskpad" rel="external"&gt;ink+volt Dashboard
Deskpad&lt;/a&gt; weekly
planner and tracker. It's very nicely designed. If you like the looks of it,
please consider ordering it from them and giving it a try. I don't regret
buying and using it myself.&lt;/p&gt;
&lt;p&gt;When I actually used this, I found it really useful. However, I've been missing
a crucial &lt;em&gt;ruthless prioritization&lt;/em&gt; step. Over time, my TODO list has exploded.
Now today, I feel buried under an impossible amount of work that while I'm
committed to finishing, is unrealistic.&lt;/p&gt;
&lt;p&gt;Today I am using this planner less and less. I fell off the wagon.&lt;/p&gt;
&lt;h3 id="my-not-so-new-new-pen-and-paper-system"&gt;My not-so-new, new pen and paper system&lt;/h3&gt;
&lt;p&gt;I decided it's time to simplify things. I've used the &lt;a href="https://en.wikipedia.org/wiki/Eisenhower_matrix" rel="external"&gt;Eisenhower
Matrix&lt;/a&gt; quite a bit in the
past to prioritize work. It's definitely not new.&lt;/p&gt;
&lt;p&gt;But my previous use of it was always in a digital form, such is on a &lt;a href="https://trello.com" rel="external"&gt;Trello
board&lt;/a&gt; or similar proprietary tools.&lt;/p&gt;
&lt;p&gt;Now I'm reviving it as a pen and paper system. This combines the simplicity of
a prioritization system I've used extensively with the pleasure of it being a
pen and paper system.&lt;/p&gt;
&lt;p&gt;So, without further ado, here it is. First, the screenshot (click for a
printable PDF):&lt;/p&gt;
&lt;p&gt;&lt;a href="https://swaits.com/prioritizing-with-eisenhower-matrix/prioritization-matrix.pdf"&gt;&lt;img alt="prioritization-matrix" src="https://swaits.com/prioritizing-with-eisenhower-matrix/prioritization-matrix.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="using-the-system"&gt;Using the system&lt;/h4&gt;
&lt;p&gt;It's pretty simple. If you're already familiar with the &lt;a href="https://en.wikipedia.org/wiki/Eisenhower_matrix" rel="external"&gt;Eisenhower
Matrix&lt;/a&gt;, you can probably skip
this section. But, in case you're not, here's how it works:
w
&lt;strong&gt;Add tasks:&lt;/strong&gt; As I add each task, I think about its importance and its
urgency. Then I write it in the matching box.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Follow the priorities:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Execute&lt;/strong&gt;: These are important and urgent. They're things I need to
complete now.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Schedule&lt;/strong&gt;: These are important, but not urgent. I need to schedule or
otherwise plan for how I'll get to them soon. But not today.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redirect&lt;/strong&gt;: Tasks that are urgent, but not important. I should find ways
to distribute these to teammates, friends, or family members (depending on
the context) to see if they can help. Not in a "here, do my stuff" sort of way.
More in a "hey, are you interested in helping with this" way.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Abandon&lt;/strong&gt;: Not important and not urgent. In practice, I've found that
I'll &lt;em&gt;never&lt;/em&gt; get to these. So, cut to the chase. Delete them.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In summary, we only do work we deem &lt;em&gt;important&lt;/em&gt;, and we do it or plan to do it
in order of &lt;em&gt;urgency&lt;/em&gt;. All other work is delegated or discarded.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do the work:&lt;/strong&gt; That's the point of this whole thing! Whenever I'm ready to
start a new task, I pay attention to these priorities and let them guide my
choice.&lt;/p&gt;
&lt;p&gt;Remember, there are dopamine hits coming your way as you complete tasks and
physically check them off.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Create a ritual:&lt;/strong&gt; Each week I print a new blank sheet and transfer any
incomplete tasks over. As I copy each task, I reassess its priority and
importance. These things do change over time.&lt;/p&gt;
&lt;p&gt;It's also a good opportunity to commit to the &lt;em&gt;Abandon&lt;/em&gt; step and actually drop
the tasks I'll never get to. That's why it's &lt;em&gt;ruthless&lt;/em&gt; prioritization.&lt;/p&gt;
&lt;h4 id="download-the-pdf-or-copy-the-google-sheet-template"&gt;Download the PDF or copy the Google Sheet template&lt;/h4&gt;
&lt;p&gt;If you want to try this for yourself, please feel free to grab the PDF and
print it, or copy the Google Sheet using the &lt;code&gt;File -&amp;gt; Copy&lt;/code&gt; menu and
personalize it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Download and print the &lt;a href="https://swaits.com/prioritizing-with-eisenhower-matrix/prioritization-matrix.pdf"&gt;Prioritization Matrix (US Letter, PDF)&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Copy the &lt;a href="https://docs.google.com/spreadsheets/d/1UK9fJRPcSDd8gUeAO8GLue3c3FwWMVhF24k1pfZuE7E/edit?usp=sharing" rel="external"&gt;Prioritization Matrix (Google
Sheet)&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Enjoy!&lt;/p&gt;</description><author>swaits.com</author><pubDate>Wed, 01 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://swaits.com/prioritizing-with-eisenhower-matrix/</guid></item><item><title>November 2023 - Bookmarks</title><link>https://domenicoluciani.com/2023/11/01/bookmarks.html</link><description>Bookmarks for November 2023: 13 links - Confusing git terminology; In a git repository, where do your files live?; Working without mocks - Learn Go with tests, and more.</description><author>Domenico Luciani</author><pubDate>Wed, 01 Nov 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://domenicoluciani.com/2023/11/01/bookmarks.html</guid></item><item><title>Random</title><link>https://www.xorvoid.com/random.html</link><description>Random</description><author>xorvoid</author><pubDate>Tue, 31 Oct 2023 21:20:16 GMT</pubDate><guid isPermaLink="true">https://www.xorvoid.com/random.html</guid></item><item><title>Real name policies</title><link>https://seirdy.one/notes/2023/10/31/real-name-policies/</link><description>&lt;aside&gt;&lt;p&gt;
	Reply to &lt;span class="h-cite u-in-reply-to"&gt;&lt;cite class="p-name"&gt;&lt;a class="u-url" href="https://fosstodon.org/@drewdevault/111329549329662520"&gt;Send me your thoughts on “real name” policies in free software contributions&lt;/a&gt;&lt;/cite&gt; by
		&lt;span class="h-card vcard p-author"&gt;&lt;a class="u-url url" href="https://drewdevault.com/"&gt;&lt;span class="p-name fn n"&gt;Drew Devault&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;
	&lt;/span&gt;
&lt;/p&gt;&lt;/aside&gt;&lt;p&gt;The primary, hopefully-unintended function of a “real-name policy” is to exclude people and make people &lt;em&gt;less&lt;/em&gt; genuine. Many aren’t at home with the name deemed by society to be “real”.&lt;/p&gt;&lt;p&gt;There’s plural systems. And trans users who haven’t made a full break from their deadname(s) yet. And at-risk users who would be doxed by such a policy. And folks who simply feel more comfortable with online handles than meatspace names, for no special reason in particular.&lt;/p&gt;&lt;p&gt;What incredible benefits could be worth shafting all these people? Assuming you even manage to pull it off; see &lt;span class="h-cite"&gt;&lt;cite class="p-name"&gt;&lt;a class="u-url" href="https://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/"&gt;Falsehoods Programmers Believe About Names&lt;/a&gt;&lt;/cite&gt; by &lt;span class="h-card vcard p-author"&gt;&lt;a class="u-url url" href="https://www.kalzumeus.com/"&gt;&lt;span class="p-name fn n"&gt;&lt;span class="p-given-name given-name"&gt;Patrick&lt;/span&gt;&lt;span class="p-family-name family-name"&gt;McKenzie&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;</description><author>All content on Seirdy’s Home</author><pubDate>Tue, 31 Oct 2023 20:11:51 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/notes/2023/10/31/real-name-policies/</guid></item><item><title>WCAG 2.2 and validation</title><link>https://seirdy.one/notes/2023/10/06/wcag-2.2-and-validation/</link><description>&lt;p&gt;&lt;abbr title="Web Content Accessibility Guidelines"&gt;WCAG&lt;/abbr&gt; 2.2 removed &lt;a href="https://www.w3.org/WAI/WCAG21/Understanding/parsing.html"&gt;SC 4.1.1, Parsing (Level A)&lt;/a&gt;. I maintain that valid markup has important benefits despite no longer being required. We may find it &lt;em&gt;possible&lt;/em&gt; to write good software without static analysis, construct a building without blueprints, or make an accessible website without validation. They remain good practices.&lt;/p&gt;&lt;p&gt;&lt;abbr&gt;WCAG’s&lt;/abbr&gt; normative text describes outcomes, not how to achieve them. Validation is but one part of the process of achieving the necessary outcomes.&lt;/p&gt;&lt;p&gt;Let’s not interpret the removal of SC 4.1.1 as permission to ship known errors.&lt;/p&gt;</description><author>All content on Seirdy’s Home</author><pubDate>Tue, 31 Oct 2023 19:56:05 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/notes/2023/10/06/wcag-2.2-and-validation/</guid></item><item><title>The Thinning of the Hair</title><link>https://callmeo.live/blog/the-thinning-of-the-hair/</link><description>&lt;p&gt;I had a lovely conversation with my mother this week. I can&amp;rsquo;t remember what on Earth we were blabbering on about, but as I knelt down to give one of the dogs a hug, she shrieked something unforgettable: &lt;em&gt;Your hair is thinning!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I couldn&amp;rsquo;t tell who was more shocked: my mum at the sight of my head, or myself. Especially when I&amp;rsquo;ve been buzzing my head for years now. It&amp;rsquo;s not like I still have the wild mop I had as a teen!&lt;/p&gt;
&lt;p&gt;Growing up, baldness was always my very own sword of Damocles. My old man is bald, every male on his side of the family is bald, and as my brothers hit their twenties it became apparent they wouldn&amp;rsquo;t have their cranial insulation for long, and I was soon to follow.&lt;/p&gt;
&lt;p&gt;In 2019&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; I got the clippers out and took the plunge	 for some daft reasons: I was fed up of maintaining my hair, and &lt;a href="https://www.theguardian.com/lifeandstyle/ng-interactive/2019/apr/13/stephen-collins-on-barber-shops-cartoon"&gt;going to the barber&amp;rsquo;s is a pain&lt;/a&gt;. A weekly one and done took less effort, was much cheaper, and hey it turns out I look good without hair. Hell, I even picked up a safety razor for when I want to get closer.&lt;/p&gt;
&lt;p&gt;Now I get it, baldness is scary. Despite the amount of bald folk out there, there&amp;rsquo;s still a ton of prejudices against it. It also sucks to have a core part of your identity and the way people perceive you be snatched from you without control. It&amp;rsquo;s okay to feel vulnerable about your hair, and I know how useless it is for me to say &lt;em&gt;I just buzzed it mate simple&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Despite my hair not getting longer than 4mm these days, there&amp;rsquo;s still a hint of worry about my hair thinning. While I have no interest in returning to my teen mop, there&amp;rsquo;s something disconcerting about the fact that eventually I won&amp;rsquo;t be able to do anything with it, even if I don&amp;rsquo;t really want to. Could settle for a monk haircut at least.&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;At the end of the day I&amp;rsquo;m not an authority on hair loss, not having to faff about with it is good enough for me. The only downsides I can come up are that my ears are marginally colder in winter and sticking your head out a car window doesn&amp;rsquo;t feel as satisfying.&lt;/p&gt;
&lt;p&gt;And with all that said and done, if the fates wish to take my goatee away they can pry it from my cold, dead &lt;del&gt;hands&lt;/del&gt; chin.&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;For the sake of my ego I have to make it clear that this was &lt;em&gt;January&lt;/em&gt; 2019, way before the Covid pandemic. I was so unique, and then everyone else did the same.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;During the writing of this, I found out that the &lt;em&gt;monk haircut&lt;/em&gt; is called a &lt;a href="https://en.wikipedia.org/wiki/Tonsure"&gt;Tonsure&lt;/a&gt;.``&amp;#160;&lt;a class="footnote-backref" href="#fnref:2"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>callmeolive</author><pubDate>Tue, 31 Oct 2023 19:10:00 GMT</pubDate><guid isPermaLink="true">https://callmeo.live/blog/the-thinning-of-the-hair/</guid></item><item><title>What Is Going On With Youtube Ads?</title><link>https://matteoraso.github.io/what-is-going-on-with-youtube-ads.html</link><description>&lt;p&gt;I've recently found myself watching a lot of Youtube videos on my flip phone because I have no audio on my new desktop (thanks, Linux). This means getting hit with ads designed to be seen by people with no viewer history, since my phone doesn't have enough RAM for cookies …&lt;/p&gt;</description><author>Matteo's Website</author><pubDate>Tue, 31 Oct 2023 06:00:00 GMT</pubDate><guid isPermaLink="true">https://matteoraso.github.io/what-is-going-on-with-youtube-ads.html</guid></item><item><title>Hunting a spooky ethernet driver bug</title><link>https://mattkeeter.com/blog/2023-10-31-dma</link><description/><author>Matt Keeter</author><pubDate>Tue, 31 Oct 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://mattkeeter.com/blog/2023-10-31-dma</guid></item><item><title>Is Scrum Democratic?</title><link>https://backendhance.com/blog/2023/is-scrum-democratic/</link><description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;p&gt;Ist Scrum demokratisch?&lt;/p&gt;
&lt;p&gt;Diese Frage kommt immer wieder auf.&lt;/p&gt;
&lt;p&gt;Immer wieder bin ich in Teams in denen es auf diese Frage hinausläuft.&lt;/p&gt;
&lt;p&gt;Das Stichwort ist immer wieder &lt;strong&gt;&amp;ldquo;selbstorganisiertes Team&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id="wie-weit-entscheidet-ein-selbstorganisiertes-team"&gt;
&lt;a class="anchor" href="#wie-weit-entscheidet-ein-selbstorganisiertes-team"&gt;Wie weit entscheidet ein selbstorganisiertes Team?&lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Unumschritten: Das Team organisiert sich die Arbeit selbst.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Der Product Owner entscheidet welche &lt;em&gt;Aufgaben&lt;/em&gt; in welcher &lt;em&gt;Reihenfolge&lt;/em&gt; abgearbeitet werden.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Und er ist ein Teil des Teams.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Auch unumschritten: Die Entwickler &lt;em&gt;planen einen Sprint&lt;/em&gt; und definieren wann ein Thema &amp;ldquo;fertig&amp;rdquo; ist.&lt;/p&gt;</description><author>Backendhance</author><pubDate>Tue, 31 Oct 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/is-scrum-democratic/</guid></item><item><title>Wellness challenge for November 2023</title><link>https://benovermyer.com/blog/2023/10/wellness-challenge-for-november-2023/</link><description>&lt;p&gt;Last month, I went over my goals for this year to remind myself what they were and see how I'm doing.
It was an unpleasant surprise; I'd failed to do any of them. Then, earlier this month, I read Arnold
Schwarzenegger's "Be Useful" book. His stories about growing up and getting into bodybuilding made my
brain start working.&lt;/p&gt;
&lt;p&gt;After a few days of reflection, I decided to do a wellness challenge for myself over the course of November.
There are two components to this challenge:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;No alcohol&lt;/li&gt;
&lt;li&gt;Gym workouts at least four days a week&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The goal with the first is to eliminate a source of empty calories and to give my body a break from
the toxic effects.&lt;/p&gt;
&lt;p&gt;The goal with the second is threefold - first, to establish a gym habit; second, to lose useless weight; and third,
to try out bodybuilding as a hobby.&lt;/p&gt;
&lt;p&gt;Giving up alcohol is not new to me. I gave it up for two months last November. I felt pretty great even just a couple
weeks into it, so I'm looking forward to this.&lt;/p&gt;
&lt;p&gt;For the workouts, I intend to go in the mornings before work. The gym that I go to is empty then, and it might
be a good wake-up routine. I've chosen &lt;a href="https://www.muscleandstrength.com/workouts/start-from-scratch-beginner-workout" rel="external"&gt;this workout plan&lt;/a&gt;
from Muscle and Strength. The first couple weeks use this schedule:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Squats, 2 sets, 12 reps&lt;/li&gt;
&lt;li&gt;Leg curls, 1 set, 12 reps&lt;/li&gt;
&lt;li&gt;Calf raises, 2 sets, 12 reps&lt;/li&gt;
&lt;li&gt;Bench press, 2 sets, 12 reps&lt;/li&gt;
&lt;li&gt;Pulldowns, 2 sets, 12 reps&lt;/li&gt;
&lt;li&gt;Cable rows, 2 sets, 12 reps&lt;/li&gt;
&lt;li&gt;Overhead press, 2 sets, 12 reps&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Tomorrow morning I'll need to figure out what weight I should be using for each. I will be logging my workouts so
I can figure out how I'm doing over time. I know how to do all of the above exercises except for the overhead press,
so I'll need to make sure I get the proper form for that one before I really push it.&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Tue, 31 Oct 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2023/10/wellness-challenge-for-november-2023/</guid></item><item><title>How I Do Things: Chronic Fatigue Edition</title><link>https://taylor.town/how-i-do-things-cf</link><description>I feel like a battery that can't keep its charge.</description><author>taylor.town</author><pubDate>Tue, 31 Oct 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/how-i-do-things-cf</guid></item><item><title>Running bare metal PostgreSQL on ZFS</title><link>https://ellie.wtf/notes/postgres-on-zfs/</link><description>&lt;p&gt;I'm setting up new &lt;a href="https://ellie.wtf/notes/postgresql/"&gt;postgres&lt;/a&gt; servers for &lt;a href="https://ellie.wtf/projects/atuin/"&gt;Atuin&lt;/a&gt;! We're going with a hot replica this time, and making things &lt;em&gt;much&lt;/em&gt; more reliable. Atuin has had no outages or database issues in a couple of years, but I don't want to push my luck.&lt;/p&gt;
&lt;p&gt;You might also be interested in the &lt;a href="https://ellie.wtf/notes/hetzner-k3s/"&gt;hetzner k3s&lt;/a&gt; setup I did for the Atuin api images&lt;/p&gt;
&lt;p&gt;I'm going to be doing a fairly minimal setup to begin with, and tune things later. I'm not sure which options will best suit my workload, so will keep things simple.&lt;/p&gt;
&lt;p&gt;Note that the Atuin queries are not complicated. We mostly just store a pretty high volume of data, and read sequentially (ish). There are minimal joins, and minimal complex queries.&lt;/p&gt;
&lt;p&gt;We will often have bursts where 10s/100s of thousands of rows need to be written or read as quickly as possible - however this is also pretty sequential and not at all "complex".&lt;/p&gt;
&lt;p&gt;Ideally, we will compress the data as much as possible. While Atuin mostly stores encrypted data (which does not compress well), there is a decent amount of unencrypted data in the form of JSON padding + timestamps etc. Compress them! This will nicely reduce disk usage, but also IO - at the cost of some CPU.&lt;/p&gt;
&lt;p&gt;As our queries are simple, the CPU cost is fine.&lt;/p&gt;
&lt;h2 id="setting-up"&gt;Setting up&lt;/h2&gt;
&lt;p&gt;I'm running this on a couple of hetzner machines I bought in an auction. They have Ryzen CPUs, and 4x 1tb nvme SSD.&lt;/p&gt;
&lt;p&gt;Two of the SSDs will be running a simple RAID mirror, and will store the OS and logs. The other two will have my ZFS filesystem + postgres on them.&lt;/p&gt;
&lt;p&gt;This costs me 50% of my storage in mirroring, however as this is bare metal hardware there's a chance that a disk could fail. I'd like to ensure my database can continue running until I failover to the replica + a technician can replace the disk.&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #928374; font-style: italic;"&gt;# Install zfs&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;apt&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; install zfsutils-linux&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #928374; font-style: italic;"&gt;# check version&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;zfs&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; version&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check your disk layout with &lt;code&gt;lsblk&lt;/code&gt;&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;NAME        MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINTS&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;nvme1n1     259:0    0 953.9G  0 disk&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;├─nvme1n1p1 259:1    0    32G  0 part&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;│ └─md0       9:0    0    32G  0 raid1 [SWAP]&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;├─nvme1n1p2 259:2    0     1G  0 part&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;│ └─md1       9:1    0  1022M  0 raid1 /boot&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;├─nvme1n1p3 259:3    0   128G  0 part&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;│ └─md2       9:2    0 127.9G  0 raid1 /var&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;├─nvme1n1p4 259:4    0     1K  0 part&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;└─nvme1n1p5 259:5    0 792.9G  0 part&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  └─md3       9:3    0 792.7G  0 raid1 /&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;nvme0n1     259:6    0 953.9G  0 disk&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;nvme3n1     259:7    0 953.9G  0 disk&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;nvme2n1     259:8    0 953.9G  0 disk&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;├─nvme2n1p1 259:9    0    32G  0 part&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;│ └─md0       9:0    0    32G  0 raid1 [SWAP]&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;├─nvme2n1p2 259:10   0     1G  0 part&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;│ └─md1       9:1    0  1022M  0 raid1 /boot&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;├─nvme2n1p3 259:11   0   128G  0 part&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;│ └─md2       9:2    0 127.9G  0 raid1 /var&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;├─nvme2n1p4 259:12   0     1K  0 part&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;└─nvme2n1p5 259:13   0 792.9G  0 part&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  └─md3       9:3    0 792.7G  0 raid1 /&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;/dev/nvme1n1&lt;/code&gt; and &lt;code&gt;/dev/nvme2n1&lt;/code&gt; are both in use for my OS, but &lt;code&gt;/dev/nvme0n1&lt;/code&gt; and &lt;code&gt;/dev/nvme3n1&lt;/code&gt; are leftover for my zfs mirror.&lt;/p&gt;
&lt;p&gt;Create the mirror&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;zpool&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; create postgres mirror&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; -o&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; ashift=&lt;/span&gt;&lt;span style="color: #D3869B;"&gt;12&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; /dev/nvme0n1 /dev/nvme3n1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It returned nice and fast!&lt;/p&gt;
&lt;p&gt;Checking again with &lt;code&gt;lsblk&lt;/code&gt; shows some usage&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;nvme0n1     259:6    0 953.9G  0 disk&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;├─nvme0n1p1 259:14   0 953.9G  0 part&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;└─nvme0n1p9 259:15   0     8M  0 part&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;nvme3n1     259:7    0 953.9G  0 disk&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;├─nvme3n1p1 259:18   0 953.9G  0 part&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;└─nvme3n1p9 259:19   0     8M  0 part&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Confirmed with &lt;code&gt;zpool status&lt;/code&gt;&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;  pool: postgres&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt; state: ONLINE&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;config:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;	NAME         STATE     READ WRITE CKSUM&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;	postgres     ONLINE       0     0     0&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;	  mirror-0   ONLINE       0     0     0&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;	    nvme0n1  ONLINE       0     0     0&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;	    nvme3n1  ONLINE       0     0     0&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;errors: No known data errors&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The pool is mounted by default at &lt;code&gt;/postgres&lt;/code&gt;. Nice!&lt;/p&gt;
&lt;h2 id="postgres"&gt;Postgres&lt;/h2&gt;
&lt;p&gt;Before we can setup datasets, we need to setup postgres. Postgres needs to run its init &lt;em&gt;before&lt;/em&gt; we move it to zfs. Annoying, but ok.&lt;/p&gt;
&lt;p&gt;Ubuntu 22.04 has a pretty old version in its repos, so install the postgres one&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;sh&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; -c&lt;/span&gt;&lt;span style="color: #A89984;"&gt; '&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;echo &amp;quot;deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main&amp;quot; &amp;gt; /etc/apt/sources.list.d/pgdg.list&lt;/span&gt;&lt;span style="color: #A89984;"&gt;'&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;wget&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; --quiet -O&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; - https://www.postgresql.org/media/keys/ACCC4CF8.asc&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; |&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt; sudo&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; apt-key add -&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;apt&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; update&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;apt&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; install postgresql-16 postgresql-contrib-16&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;systemctl&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; enable postgresql&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;systemctl&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; start postgresql&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, stop postgres. Move its data to a temp location, create the datasets, and move things back again. Could probably rsync instead of mv/cp to keep permissions.&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;systemctl&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; stop postgresql&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #928374; font-style: italic;"&gt;# move postgres data to temp&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;mv&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; /var/lib/postgresql/16/main/pg_wal /tmp/pg_wal&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;mv&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; /var/lib/postgresql /tmp/postgresql&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #928374; font-style: italic;"&gt;# create the datasets&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;zfs&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; create postgres/data&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; -o&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; mountpoint=/var/lib/postgresql&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;zfs&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; create postgres/wal&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; -o&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; mountpoint=/var/lib/postgresql/16/main/pg_wal&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #928374; font-style: italic;"&gt;# switcharoo the data back&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;cp&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; -r&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; /tmp/postgresql/&lt;/span&gt;&lt;span style="color: #83A598;"&gt;*&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; /var/lib/postgresql&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;cp&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; -r&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; /tmp/pg_wal/&lt;/span&gt;&lt;span style="color: #83A598;"&gt;*&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; /var/lib/postgresql/16/main/pg_wal&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #928374; font-style: italic;"&gt;# sort perms&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;chmod&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; -R 0700&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; /var/lib/postgresql&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;chmod&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; -R 0700&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; /var/lib/postgresql/16/main/pg_wal&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;chown&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; -R&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; postgres: /var/lib/postgresql&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #928374; font-style: italic;"&gt;# start postgres once more&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;systemctl&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; start postgresql&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check all is ok:&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;zfs&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; list&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;NAME&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;                 USED  AVAIL     REFER  MOUNTPOINT&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;postgres&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;             600K   922G       24K  /postgres&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;postgres/db&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;           72K   922G       24K  /postgres/db&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;postgres/db/base&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;      24K   922G       24K  /postgres/db/base&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;postgres/db/pg_wal&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;    24K   922G       24K  /postgres/db/pg_wal&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="configuring-zfs"&gt;Configuring ZFS&lt;/h2&gt;
&lt;p&gt;I've read a bunch, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;https://vadosware.io/post/everything-ive-seen-on-optimizing-postgres-on-zfs-on-linux/&lt;/li&gt;
&lt;li&gt;https://bun.uptrace.dev/postgres/tuning-zfs-aws-ebs.html&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One of the above articles had issues where some nvme hardware was reporting a successful write when using &lt;code&gt;fdatasync&lt;/code&gt;, but not actually writing ok. Many drives will report a successful write once the data has been stored in the volatile write cache, but not actually stored. Disable this with:&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;apt install nvme-cli&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;nvme set-feature -f 6 -v 0 /dev/nvme0n1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;nvme set-feature -f 6 -v 0 /dev/nvme3n1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I spend a while thinking about the optimal &lt;code&gt;recordsize&lt;/code&gt;. While I'd get a higher tps running recordsize = postgres block size = 8kb, Atuin is largely sequential + reads/writes large amounts of data. I could change the postgres block size (it's a compilation option), but I'll consider trying that in the future.&lt;/p&gt;
&lt;p&gt;To begin with, I'll try the default of 128k and see how it goes. Lower numbers are potentially faster, but higher numbers tend to get better compression. It depends on a lot of factors though, so I'll measure things and see.&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #928374; font-style: italic;"&gt;# enable compression&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;zfs&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; set compression=zstd-3 postgres&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #928374; font-style: italic;"&gt;# disable access time (so, so many writes...)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;zfs&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; set atime=off postgres&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #928374; font-style: italic;"&gt;# enable improved extended attributes&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;zfs&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; set xattr=sa postgres&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #928374; font-style: italic;"&gt;# zfs set recordsize=16k postgres&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I then set &lt;code&gt;full_page_writes = off&lt;/code&gt; on the postgres side - zfs cannot write partial pages so it's pretty redundant.&lt;/p&gt;
&lt;p&gt;Next up, we'll give the ARC (ZFS page cache) 75% of the system memory. The remainder will be used by postgres shared_buffers.&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;echo 51539607552 &amp;gt;&amp;gt; /sys/module/zfs/parameters/zfs_arc_max&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To persist across reboots, set this in  &lt;code&gt;/etc/modprobe.d/zfs.conf&lt;/code&gt;&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;options zfs zfs_arc_max=51539607552&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="postgres-config"&gt;Postgres config&lt;/h2&gt;
&lt;p&gt;Not specific to ZFS, but some general postgres tuning&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;# 25% of 64GB&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;shared_buffers = 16GB&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;work_mem = 8MB&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;# make vaccuums/etc faster&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;maintenance_work_mem = 1GB&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;# tell the planner how much the ZFS ARC will likely cache&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;effective_cache_size = 48GB&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There's a bunch more tuning we can do, but realistically this will help the most. Postgres has a pretty tiny amount of memory configured by default!&lt;/p&gt;
&lt;p&gt;At this point, I rebooted to ensure everything was OK + persisted properly.&lt;/p&gt;
&lt;h2 id="chuck-load-at-it"&gt;Chuck load at it&lt;/h2&gt;
&lt;p&gt;I'd like to ensure my system performs at least mostly ok, so I ran pgbench and got&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;scaling factor: 50&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;query mode: simple number of clients: 20 &lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;number of threads: 4 &lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;maximum number of tries: 1 &lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;number of transactions per client: 100000 &lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;number of transactions actually processed: 2000000/2000000 &lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;number of failed transactions: 0 (0.000%) &lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;latency average = 2.863 ms &lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;initial connection time = 10.287 ms &lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;tps = 6984.804317 (without initial connection time)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Not bad! Not that representative of my workload, but at least an indication it's not totally broken. I'll tune this some more in the future.&lt;/p&gt;
&lt;h2 id="next"&gt;Next&lt;/h2&gt;
&lt;p&gt;Next I'll be configuring backups with pgbackrest, and setting up a hot standby we can failover to should things go wrong!&lt;/p&gt;
&lt;p&gt;Then copying the dataset across, and making this new database production 🚀&lt;/p&gt;</description><author>Ellie's Notes</author><pubDate>Tue, 31 Oct 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ellie.wtf/notes/postgres-on-zfs/</guid></item><item><title>Exploring Pytest Fixtures: Notes and Examples</title><link>https://www.vladsiv.com/posts/pytest-fixtures-notes-examples</link><description>Here, I present a compilation of notes and practical scenarios drawn from my experiences, demonstrating the effective utilization of pytest fixtures. These examples provide valuable insights into leveraging fixtures to refine and improve the architecture of your testing module.</description><author>vladsiv</author><pubDate>Tue, 31 Oct 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.vladsiv.com/posts/pytest-fixtures-notes-examples</guid></item><item><title>SDXL LoRA Training</title><link>https://smcleod.net/2023/10/sdxl-lora-training/</link><description>A talk I gave to some peers on creating your own SDXL LoRA models from my tinkering around over the last few weeks.</description><author>smcleod.net</author><pubDate>Mon, 30 Oct 2023 22:00:00 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2023/10/sdxl-lora-training/</guid></item><item><title>Note 121</title><link>https://qubyte.codes/notes/1698678096122</link><description>&lt;p&gt;Big skies over Brighton these days.&lt;/p&gt;

    &lt;img alt="The view from my home towards the ocean off of Brighton. A sliver of the sea can be seen over the houses, and you can just about make out the wind farm. Above are some dramatic clouds of various kinds. Above those the sky is blue." src="https://qubyte.codes/images/1698677990783.jpeg" /&gt;</description><author>Qubyte Codes</author><pubDate>Mon, 30 Oct 2023 17:01:36 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/notes/1698678096122</guid></item><item><title>Paper Hoard</title><link>https://lovebloodrhetoric.com/2023/10/29/paper-hoard/</link><description>Harold had expected the last dragon would be &amp;#8230; larger. “Disappointing, I know.” The dragon, barely three feet long, spoken in a voice like a bamboo flute—high, thin and melancholy. “Hardly a challenge at all. Go ahead, kill me. Get it over with.” “I’m not here to kill you,” Harold said. “You’re a link to [&amp;#8230;]</description><author>Love, Blood &amp;amp; Rhetoric</author><pubDate>Mon, 30 Oct 2023 07:05:19 GMT</pubDate><guid isPermaLink="true">https://lovebloodrhetoric.com/2023/10/29/paper-hoard/</guid></item><item><title>How I smash burgers</title><link>https://xenodium.com/how-i-smash-burgers</link><description>&lt;p&gt;I'm neither a burger expert nor a connoisseur of any kind, yet I sure have a lot of fun smashing burgers at home. Needless to say, I shamelessly enjoy gobbling them too!&lt;/p&gt;
&lt;figure width="60%"&gt;
&lt;img src="https://xenodium.github.io/images/how-i-smash-burgers/burger-cut.png" /&gt;
&lt;figcaption&gt;my smash burger&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I'll share details on how I smash my burgers, but here's a quick ingredient list, if that's all you need.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mince beef (20%-30% fat).&lt;/li&gt;
&lt;li&gt;Streaky bacon.&lt;/li&gt;
&lt;li&gt;Brioche burger buns.&lt;/li&gt;
&lt;li&gt;American cheese slices (cheddar individual slices work too).&lt;/li&gt;
&lt;li&gt;Lettuce.&lt;/li&gt;
&lt;li&gt;Tomatoes.&lt;/li&gt;
&lt;li&gt;Onions.&lt;/li&gt;
&lt;li&gt;Pickles.&lt;/li&gt;
&lt;li&gt;Jalapeños.&lt;/li&gt;
&lt;li&gt;Garlic.&lt;/li&gt;
&lt;li&gt;Chipotle powder.&lt;/li&gt;
&lt;li&gt;Mayonnaise.&lt;/li&gt;
&lt;li&gt;Salt.&lt;/li&gt;
&lt;li&gt;Pepper.&lt;/li&gt;
&lt;li&gt;Oil.&lt;/li&gt;
&lt;li&gt;Greaseproof paper.&lt;/li&gt;
&lt;li&gt;Butter.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The calling&lt;/h2&gt;
&lt;p&gt;My quest to smash burgers at home didn't start until earlier this year, while watching the &lt;a href="https://www.imdb.com/title/tt9764362/"&gt;The Menu&lt;/a&gt;. I just could't stop &lt;a href="https://indieweb.social/@xenodium/109734285674122246"&gt;craving the burger&lt;/a&gt; from that scene, so I set out to start smashing my own.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/how-i-smash-burgers/the-menu.webp" /&gt;&lt;/p&gt;
&lt;h2&gt;The gear&lt;/h2&gt;
&lt;p&gt;Don't rush to buy anything fancy. Your existing gear will likely do the job just fine. I'd say try a few things out and only upgrade when needed. I'll share the gear I use and where I felt I needed tweaking.&lt;/p&gt;
&lt;h2&gt;Skillet&lt;/h2&gt;
&lt;p&gt;While I didn't have a griddle at home, I did have a couple of trusty &lt;a href="https://www.lodgecastiron.com/"&gt;Lodge&lt;/a&gt; &lt;strong&gt;skillets&lt;/strong&gt; (&lt;a href="https://www.lodgecastiron.com/product/round-cast-iron-classic-skillet?sku=L8SK3"&gt;cast iron&lt;/a&gt; and &lt;a href="https://www.lodgecastiron.com/product/carbon-steel-skillet?sku=CRS12"&gt;carbon steel&lt;/a&gt;). Both work great for burgers, though I have a slight preference for the carbon steel one, as it's the bigger of the two and gives a little more room for manoeuvring, specially when smashing two burgers at a time.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/how-i-smash-burgers/cast-iron.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/how-i-smash-burgers/carbon.png" /&gt;&lt;/p&gt;
&lt;p&gt;Heat the skillet up and add a little oil. If the oil starts smoking, be quick to drop the patties and start smashing.&lt;/p&gt;
&lt;h2&gt;Grill Spatula (too big/stiff for skillet)&lt;/h2&gt;
&lt;p&gt;Somewhat inspired by the film, I got myself a wide spatula so I could firmly press those patties against the skillet, and to flip of course.&lt;/p&gt;
&lt;p&gt;While this kind of spatula may work well on a spacious griddle, I felt constrained on a relatively small cast iron. Specially when flipping. I went looking for an alternative.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/how-i-smash-burgers/spatula.png" /&gt;&lt;/p&gt;
&lt;h2&gt;Spatula + smasher (my winning combo)&lt;/h2&gt;
&lt;p&gt;Over at the &lt;a href="https://reddit.com/r/castiron/"&gt;r/castiron&lt;/a&gt; subreddit, I discovered fish spatulas. They are fairly agile on cast irons but also work great for loosening burger patties before flipping.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/how-i-smash-burgers/ready.webp" /&gt;&lt;/p&gt;
&lt;p&gt;While effective for flipping, fish spatulas are obviously no good for smashing. So I got myself a burger smasher. This combo worked well for me.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/how-i-smash-burgers/smash.webp" /&gt;&lt;/p&gt;
&lt;p&gt;When smashing, use greaseproof paper to prevent the patties from sticking to the smasher.&lt;/p&gt;
&lt;h2&gt;Ingredients&lt;/h2&gt;
&lt;p&gt;While I've drawn inspiration from others, I've landed on my own preferred ingredients. I'm sure that will continue changing over time. Pick and choose as your heart desires.&lt;/p&gt;
&lt;h2&gt;Minced/ground beef&lt;/h2&gt;
&lt;p&gt;Minced beef with higher fat content (around 20-30%) is often recommended for a couple of reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Flavour: Fat equals flavour in cooking. The higher fat content will melt during cooking and become 'self-basting', resulting in a juicier and more flavourful burger.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Texture: The fat in the beef melts under heat, helping the burger achieve a crispy, caramelized exterior known as the &lt;a href="https://en.wikipedia.org/wiki/Maillard_reaction"&gt;Maillard reaction&lt;/a&gt;, which contrasts nicely against the soft, juicy interior.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the UK, I can typically find minced beef with 15%-20% fat content at the main supermarkets.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/how-i-smash-burgers/pattie.png" /&gt;&lt;/p&gt;
&lt;p&gt;Be sure to salt and pepper to taste (as in picture) on one side. Once flipped on pan, salt and pepper the other side.&lt;/p&gt;
&lt;h2&gt;Bacon&lt;/h2&gt;
&lt;p&gt;I tend to prefer smoked streaky bacon, but hey these will be your burgers. Your burgers, your rules.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/how-i-smash-burgers/streaky.png" /&gt;&lt;/p&gt;
&lt;h2&gt;Buns (brioche)&lt;/h2&gt;
&lt;p&gt;I hear potato buns are great for burgers. I've yet to try them. So far, I've settled for brioche. I happen to find these near me, so I've gone with them.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/how-i-smash-burgers/brioche.png" /&gt;&lt;/p&gt;
&lt;p&gt;Butter the buns and brown on the skillet for a minute. Check the buns often. Brioche buns can burn quickly.&lt;/p&gt;
&lt;h2&gt;American cheese&lt;/h2&gt;
&lt;p&gt;American cheese is often the burger cheese of choice.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/how-i-smash-burgers/american-cheese.webp" /&gt;&lt;/p&gt;
&lt;p&gt;While American cheese isn't widely available in the UK, the individually wrapped orange-looking cheddar cheese slices work just fine.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/how-i-smash-burgers/cheese.png" /&gt;&lt;/p&gt;
&lt;h2&gt;Toppings&lt;/h2&gt;
&lt;p&gt;I like my burgers with lettuce, tomatoes, onions, pickles, and occasionally jalapeños. For pickles, I typically just take cornichons and slice them up.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/how-i-smash-burgers/toppings_x0.30.png" /&gt;&lt;/p&gt;
&lt;h2&gt;Burger sauce (chipotle/garlic/mayo)&lt;/h2&gt;
&lt;p&gt;While classic burger sauce is often made with mayo, ketchup, pickles, and mustard, I've gone fairly rogue here.&lt;/p&gt;
&lt;p&gt;You see, I love chipotle mayo. I'm also a fan of garlic mayo, so I figured why not both? Turns out these three ingredients work great together.&lt;/p&gt;
&lt;p&gt;I like to draw out the flavours by first mixing the garlic and chipotle with a little hot water.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1 garlic clove.&lt;/li&gt;
&lt;li&gt;2 teaspoons of chipotle powder.&lt;/li&gt;
&lt;li&gt;1 tablespoon of hot water.&lt;/li&gt;
&lt;li&gt;Pinch of salt.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/how-i-smash-burgers/sauce1.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/how-i-smash-burgers/sauce2.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/how-i-smash-burgers/sauce3.png" /&gt;&lt;/p&gt;
&lt;p&gt;…and then thicken with mayo.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1/4 cup of mayo.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/how-i-smash-burgers/sauce4.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/how-i-smash-burgers/sauce5.png" /&gt;&lt;/p&gt;
&lt;p&gt;These are very rough measurements, tweak to your preference. Make more garlicky, spicier, or soften things by adding garlic, chipotle, or mayo.&lt;/p&gt;
&lt;h2&gt;Assembling&lt;/h2&gt;
&lt;p&gt;I like to assemble in the following order from the bottom bun up.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Sauce on bottom bun.&lt;/li&gt;
&lt;li&gt;Lettuce.&lt;/li&gt;
&lt;li&gt;Tomatoes.&lt;/li&gt;
&lt;li&gt;Onions.&lt;/li&gt;
&lt;li&gt;2 patties (melted cheese on both).&lt;/li&gt;
&lt;li&gt;Bacon.&lt;/li&gt;
&lt;li&gt;Pickles.&lt;/li&gt;
&lt;li&gt;Jalapeños.&lt;/li&gt;
&lt;li&gt;Sauce on top bun (oops, I forgot in the picture).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/how-i-smash-burgers/open.png" /&gt;&lt;/p&gt;
&lt;p&gt;…and here's the final product.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/how-i-smash-burgers/burger-cut.png" /&gt;&lt;/p&gt;
&lt;p&gt;If you gave smashing burgers a go, I'd love to hear about it. Also any tips are very much welcome. Get in touch (&lt;a href="https://indieweb.social/@xenodium"&gt;Mastodon&lt;/a&gt; / &lt;a href="https://twitter.com/xenodium"&gt;Twitter&lt;/a&gt; / &lt;a href="https://www.reddit.com/user/xenodium"&gt;Reddit&lt;/a&gt; / &lt;a href="mailto:me__AT__xenodium.com"&gt;Email&lt;/a&gt;).&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Mon, 30 Oct 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/how-i-smash-burgers</guid></item><item><title>Partitioning The Index</title><link>https://www.marginalia.nu/log/92_multirack_drifting/</link><description>&lt;p&gt;So a bit of an update on what I&amp;rsquo;ve been working on. This will be adapted into release notes in
a while, but I haven&amp;rsquo;t quite wrapped a bow on the change set yet.&lt;/p&gt;
&lt;p&gt;Still, it has certainly been a few weeks. Didn&amp;rsquo;t quite land how busy I&amp;rsquo;ve been until I set down to
draft this post. Them&amp;rsquo;s some changes, and I&amp;rsquo;m skipping a few to keep this meandering post at a sane length.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Mon, 30 Oct 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/92_multirack_drifting/</guid></item><item><title>Python, C, Assembly - 2'500x Faster Cosine Similarity 📐</title><link>https://ashvardanian.com/posts/python-c-assembly-comparison/</link><description>From pure Python to AVX-512 assembly, optimizing cosine similarity reveals a 2,500x speedup through SIMD, FP16, and VNNI instructions on modern Intel CPUs.</description><author>Ash's Blog</author><pubDate>Mon, 30 Oct 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ashvardanian.com/posts/python-c-assembly-comparison/</guid></item><item><title>Build The Custom Feature</title><link>/2023/10/30/Build-The-Custom-Feature.html</link><description>Investors, mentors, and fellow founders often advise against tailoring features or functionality for just a handful of customers (let alone a single customer!).</description><author>rrich.io</author><pubDate>Mon, 30 Oct 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">/2023/10/30/Build-The-Custom-Feature.html</guid></item><item><title>That time I wrote malware and got caught</title><link>https://ntietz.com/blog/that-time-i-wrote-malware/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;Most of us make some bad decisions in high school.
While other people were drinking, going to parties, and who knows what else, I was doing some experimentation of my own.
I was writing my first (and only) piece of malware.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;From as early as I can remember, I've had a fascination with security.
In games, I would play rogues and try to pickpocket people or pick open locks.
This came from two inner drives.&lt;/p&gt;
&lt;p&gt;The first one is the obvious one: getting access to things you're not supposed to.
I was a curious kid&lt;sup class="footnote-reference"&gt;
  &lt;a href="https://ntietz.com/blog/that-time-i-wrote-malware/#fn-1" id="fn-ref-1"&gt;1&lt;/a&gt;
&lt;/sup&gt;
 and wanted to know everything.
Nothing seemed like it should not be my business.
So playing as a character who could get into any room was just my cup of tea: unfettered access to know what was going on.&lt;/p&gt;
&lt;p&gt;The second one was less obvious and took me a while to realize in myself: a deep desire to know how things work.
More precisely, a deep desire to &lt;em&gt;figure out&lt;/em&gt; how things work.
If I read a book about something, that's fun and I learn something.
But if I poke at a system enough to figure out how it works and why it works that way, that's deeply satisfying and such a &lt;em&gt;thrill&lt;/em&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Our high school had computers in most classrooms, and we had a few computer labs&lt;sup class="footnote-reference"&gt;
  &lt;a href="https://ntietz.com/blog/that-time-i-wrote-malware/#fn-2" id="fn-ref-2"&gt;2&lt;/a&gt;
&lt;/sup&gt;
.
Like is common with computer labs, these required logging in with your school credentials&lt;sup class="footnote-reference"&gt;
  &lt;a href="https://ntietz.com/blog/that-time-i-wrote-malware/#fn-3" id="fn-ref-3"&gt;3&lt;/a&gt;
&lt;/sup&gt;
.
Once you logged in, you had access to your personal drive (mounted at &lt;code&gt;P:\\&lt;/code&gt;, I assume for "personal") as well as a few shared drives.
Students could read files on some of these drives, and could read and write to one of them.
Is this foreshadowing? Is it ever.&lt;/p&gt;
&lt;p&gt;Another thing that our computers had was &lt;del&gt;spyware&lt;/del&gt; "monitoring software" so that the lab supervisor could see what we were doing.
On the one hand, high school students do many unwise things so this is probably a reasonable practice.
But on the other hand, it inures people to being spied on, and it definitely didn't prevent me from doing naughty things, soooo... it wasn't very effective.
To prevent us from killing the process that monitored us, we had no access to Task Manager.&lt;/p&gt;
&lt;p&gt;My junior year, I was in a programming class, and we used .NET languages (VB.NET and C#) in our classes.
Since we were using Visual Studio, we had access to a fun drag-and-drop builder, and we also had hooks into Windows APIs to do convenient things.
You could capture keystrokes, like Ctrl-C for copy if you want to do something different with it.&lt;/p&gt;
&lt;p&gt;Naturally, I wanted to explore the limts of these APIs.
What would it let me capture, and what would it not?
Unfortunately, they let me capture almost everything.
From here, I created my malware: Fluffy, Destroyer of Worlds.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Fluffy was a simple program.
When you ran it, it would expand itself to full screen and display a picture of a kitten jumping through a field, labeled with "Fluffy, Destroyer of Worlds"—in Comic Sans, of course.
Below that was a loading bar which started out quick but would slow down exponentially, so you would get to 90% quickly but then would never get to 100%.&lt;/p&gt;
&lt;p&gt;Users would sit there and wait expectantly for this program that Nicole wrote to do something cool, presumably.
But eventually they'd pick up on the gag, maybe because I was giggling.
So they would try to close it.
But I was able to capture Alt-Tab and prevent the user from changing windows.
And I was able to capture Ctrl-Q and Alt-F4 and prevent the user from closing the program.
I was &lt;em&gt;not&lt;/em&gt; able to capture Ctrl-Alt-Delete.. but that took you to a login screen that only had options to resume, log out, or restart the computer (no admin controls could even override this, shocking to me to this day).&lt;/p&gt;
&lt;p&gt;They had no choice but to log out or restart, which would make them lose any work they had open.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Like any good hacker, I developed my malware in my parents' basement.
And like any good hacker, I tested it on my friend first.
Shoutout to Andrew for running something I sent him without really questioning it.
He got off easy because his home computer did &lt;em&gt;not&lt;/em&gt; have Ctrl-Alt-Delete blocked.
We tried to transfer it to him by AIM or email, but .exe files were blocked, so naturally we transferred it by pretending it was a .zip file.&lt;/p&gt;
&lt;p&gt;Once Andrew had confirmed that it did work as expected, I carried Fluffy, Destroyer of Worlds to school with me on a flash drive.
Our computers didn't prevent running arbitrary executables, so I was able to just copy it onto my personal drive and run it.
But it was more fun if someone else ran it, so I put it on that shared drive.
(It returned!)&lt;/p&gt;
&lt;p&gt;Then I told my friends to run it.
They thought it was funny.
I had my Latin teacher run it, and she lost half a period of notes; I felt slightly bad about that.
My English teacher ran it, and he thought it was &lt;em&gt;hilarious&lt;/em&gt; even though he lost notes too.
I thought that was the end of it, I'd had my fun.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;The next day my programming teacher asked me about the program.
Apparently, some other people had run it, because they found it on the shared drive.
And some of them had our librarian run it, and hoo boy she did &lt;em&gt;not&lt;/em&gt; find it funny in the slightest.
She wanted me to immediately lose all computer privileges which, honestly, fair.&lt;/p&gt;
&lt;p&gt;My programming teacher went to bat for me, and struck a deal with IT to keep my computer privileges&lt;sup class="footnote-reference"&gt;
  &lt;a href="https://ntietz.com/blog/that-time-i-wrote-malware/#fn-4" id="fn-ref-4"&gt;4&lt;/a&gt;
&lt;/sup&gt;
.
The deal was that I had to get rid of the program and monitor for it coming back, and make sure (as far as I could) no one else was affected by it.
I deleted that copy from the shared drive but people &lt;em&gt;kept putting it back&lt;/em&gt;.
Why???
So I kept deleting it over and over, until the novelty wore off and we all forgot about it.&lt;/p&gt;
&lt;p&gt;Side note, can we just say how shocking it is that everyone ran a random executable?
That we just ran things we found?
Security understanding sure has changed over the last two decades.&lt;/p&gt;
&lt;p&gt;High school was a weird time.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;By writing some very unsophisticated malware, I learned quite a bit.&lt;/p&gt;
&lt;p&gt;I was able to explore the bounds of a system and what it was able to do.
But more importantly, I learned that writing malware wasn't harmless and could hurt other people and could also put my own activities at risk.
It scared me out of doing any sort of security work for a while.&lt;/p&gt;
&lt;p&gt;It taught me how much privilege I had.
What I did was not legal and violated school rules, and some people may have had the book thrown at them.
Instead, I had a teacher and mentor go to bat for me and ensure I could keep on learning.&lt;/p&gt;
&lt;p&gt;It also taught me about the boundaries of systems, and the ways that security features can be abused.
The ways that the systems we put in place can be exploited.
Exploring systems, boundaries, what you can and cannot do—such a great way to learn.
Just, do it with consent.&lt;/p&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 30 Oct 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/that-time-i-wrote-malware/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Taming Your Infinite Queues</title><link>https://taylor.town/infinite-queues</link><description>Strategy is the best weapon you wield against the infinite. Bad algorithms invite anxiety, overwhelmedness, and frustration. Good algorithms create pride, peace, and accomplishment.</description><author>taylor.town</author><pubDate>Mon, 30 Oct 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/infinite-queues</guid></item><item><title>Why Don't More Languages Have a call/cc Operator?</title><link>https://lambdaland.org/posts/2023-10-30_why_no_callcc/</link><description>&lt;p&gt;Something I’ve wondered about for a little while: why don’t more languages have a &lt;a class="link-external" href="https://en.wikipedia.org/wiki/Call-with-current-continuation" rel="external"&gt;&lt;code&gt;call/cc&lt;/code&gt;&lt;/a&gt; operator? Having &lt;a href="https://lambdaland.org/posts/2022-11-17_continutations/"&gt;first-class continuations&lt;/a&gt; in your programming language gives your programmers a powerful construct. So why do only a handful of languages have it?&lt;/p&gt;
&lt;p&gt;The short answer is: it’s tricky to implement efficiently. One way to get &lt;code&gt;call/cc&lt;/code&gt; is to convert your code into continuation-passing style. Then, &lt;code&gt;call/cc&lt;/code&gt; simply takes the continuation in that representation and binds it to a variable. Most languages don’t seem to go through a continuation-passing style conversion pass though, so there’s no continuation to grab.&lt;/p&gt;
&lt;p&gt;I asked &lt;a class="link-external" href="https://users.cs.utah.edu/~mflatt/" rel="external"&gt;Matthew Flatt&lt;/a&gt; about this today, and his answer was that most languages use the C model of functions: when you call a function, you push the arguments to the function onto a stack along with the return address. Then, when you return, you pop those element back off the stack. To get &lt;code&gt;call/cc&lt;/code&gt;, you’ve have to copy the entire stack and pass that around.&lt;/p&gt;</description><author>Lambda Land</author><pubDate>Mon, 30 Oct 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://lambdaland.org/posts/2023-10-30_why_no_callcc/</guid></item><item><title>October retro: Accessibility, Dev Events, Sustainability</title><link>https://holzer.online/weeknotes/2023/43/index.html</link><description>&lt;time datetime="2023-10-29 22:10"&gt;&lt;/time&gt;

&lt;p&gt;
I've recently made a few accessibility improvements for the site. For one, I finally got around to add a skip-to-content link to the navigation, mostly following along an &lt;a href="https://css-tricks.com/how-to-create-a-skip-to-content-link/"&gt;article from CSS Tricks&lt;/a&gt;, with very few tweaks. And I replaced the &lt;a href="https://www.kryogenix.org/code/browser/sorttable/"&gt;sorttable&lt;/a&gt; script for the tables of content with a custom-element I wrote, in order to make the table columns also sortable via keyboard navigation. On the same note: some time ago I wrote that &lt;a href="/articles/small-things-matter-for-a11y/"&gt;small things matter for accessibility&lt;/a&gt;, which as a general principle is true, but I recently learned that the example which triggered the article is not correct anymore, because &lt;a href="https://www.tempertemper.net/blog/bold-and-italics-arent-read-by-screen-readers"&gt;bold and italics actually aren't read by screen readers after all&lt;/a&gt;. That was a feature of the NVDA screen reader, but not a very popular one, therefore it got disabled by default. Generally there is no audible distinction between i/em or b/strong tags for screen reader users.
&lt;/p&gt;

&lt;p&gt;
Also three weeks ago, I had the pleasure to participate in my first post-pandemic in-person developer event (which incidentally has been my last pre-pandemic conference as well...), the &lt;a href="https://unkonf.de/"&gt;unKonf&lt;/a&gt; in Mannheim. The unKonf is basically a &lt;a href="https://en.wikipedia.org/wiki/BarCamp"&gt;barcamp&lt;/a&gt;, but it is traditionally opened with a keynote, which this year was given by &lt;a href="https://www.ufried.com/blog/"&gt;Uwe Friedrichsen&lt;/a&gt; on the topic of sustainability patterns for IT. One fact that suprised me was that the global IT landscape causes more greenhouse gas emissions than the traffic of the worldwide logistics industry. I can only recommend the &lt;a href="https://speakerdeck.com/ufried/patterns-of-sustainability-going-green-in-it"&gt;slide deck&lt;/a&gt;, which is filled with tons of interesting and important pointers. One take-away was that measuring and visualizing resource and energy consumption of a system is a necessary first step to move towards a more sustainable IT. Apart from that, there were many other great sessions and the "hallway track" made me realize how much I actually have missed to talk shop (outside of my actual shop).
&lt;/p&gt;</description><author>Fabian's public notepad</author><pubDate>Sun, 29 Oct 2023 23:10:00 GMT</pubDate><guid isPermaLink="true">https://holzer.online/weeknotes/2023/43/index.html</guid></item><item><title>Firefox Tabs Design</title><link>https://tbolt.space/2023/10/29/firefox-tabs-design</link><description>Over two years ago Firefox launched a new design with a change to how tabs look: Inspired tab design: Floating tabs neatly contain information and offer cues only when you need them, like visual indicators for audio controls. The rounded design of the active tab signals the ability to easily grab and move tabs as needed.</description><author>tbolt.space</author><pubDate>Sun, 29 Oct 2023 17:20:42 GMT</pubDate><guid isPermaLink="true">https://tbolt.space/2023/10/29/firefox-tabs-design</guid></item><item><title>How Acrylic Painting Makes Me a Better Remote Worker</title><link>https://www.mattp.tech/the-pittsburgh-roboticist-blog/how-acrylic-painting-makes-me-a-better-remote-worker</link><description>Exploring the unexpected intersection of acrylic painting and remote work 
during the pandemic. Dive into a personal journey that reveals how a canvas 
and brush can transform the digital workspace. From mindful breaks amidst 
screen chaos to celebrating small wins, discover how art offers refreshing 
insights for the remote worker.</description><author>The Pittsburgh Roboticist Blog - Matt Puchalski - Pittsburgh Roboticist</author><pubDate>Sun, 29 Oct 2023 17:16:00 GMT</pubDate><guid isPermaLink="true">https://www.mattp.tech/the-pittsburgh-roboticist-blog/how-acrylic-painting-makes-me-a-better-remote-worker</guid></item><item><title>Two-factor login</title><link>https://dorotac.eu/posts/2fa_login/</link><description>&lt;h1&gt;Two-factor login&lt;/h1&gt;
&lt;p&gt;Do you still type a password to get into your laptop? What about in the office? Or when you're on the train? What if there are cameras above your head? Have you ever worried that a snooper might get your password and do bad things with it?&lt;/p&gt;
&lt;p&gt;I'm sure you haven't. Like a properly security-aware hacker, you disabled password-based SSH login already. You also don't reuse your laptop's password for anything else, especially not for your backups. Even if someone snoops the password, all they get is access to the laptop itself. Right?&lt;/p&gt;
&lt;p&gt;Haha, I betcha. Let's face it: opsec is hard. Upgrading your opsec skill takes effort. Good opsec reduces the likelihood that secrets will be spilled.&lt;/p&gt;
&lt;p&gt;That's why I decided to augment my laptop with two-factor login.&lt;/p&gt;
&lt;p&gt;Now, I can whip out my laptop on the train, and log in to it without worrying too much whether the password gets intercepted. It's unique, so the attacker won't hack any of my other properties (and I'm not leaving my laptop sitting around). I see you grumble: "another unique password is hard to remember! What's the point if you could just change your login password?" The hardware token makes short and memorable passwords possible, because it bricks itself after 3 failed guesses! So I can get away with a password that's really easy to remember, without losing resistance to brute forcing.&lt;/p&gt;
&lt;p&gt;Now I can also leave the laptop in a public space for 5 minutes. Even the security guard who used the ceiling camera to record me typing the password won't get into my laptop undetected. After all, I took the hardware token with half of my login credentials with me!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WARNING:&lt;/strong&gt; I only cover the user login procedure in this guide. Not unlocking the disk encryption. The evil security guard can still compromise the laptop by watching me enter the decryption password and then rebooting the system. Stealing the laptop is not in my threat model for this blog post.&lt;/p&gt;
&lt;h2&gt;Second factor&lt;/h2&gt;
&lt;p&gt;Two-factor authentication splits your secret into pieces of two different types (factors), which have different security properties. This is to reduce the likelihood of the entire secret getting intercepted. Each factor may get intercepted easily on its own, but both at the same time is going to be much more difficult because of how they differ.&lt;/p&gt;
&lt;p&gt;There are two most common factors: "something you know", like a password, and "something you have", like a hardware token. Passwords can be intercepted by watching someone type them, or by key logging, or by retrieving it from badly secured storage. A hardware token cannot be duplicated, and its secret can only be intercepted by physically stealing the token.&lt;/p&gt;
&lt;p&gt;While hardware tokens contain some data constituting the secret, it's not possible to get that data out. If you &lt;em&gt;can&lt;/em&gt; duplicate the secret, then the token not doing its basic job as a "something you have", and stops being a second factor to a password. It basically becomes a password with extra steps.&lt;/p&gt;
&lt;h2&gt;Login setup&lt;/h2&gt;
&lt;p&gt;This guide will show you how to enable 2-factor authentication with a password and a smart card (Nitrokey Pro) on Fedora 37. It does not disable the original password, so that locking yourself out is harder.&lt;/p&gt;
&lt;p&gt;First, install some packages:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo dnf install sssd opensc p11-kit gnutls-utils nitrokey-app easy-rsa
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let's play around with the smart card itself. Plug it into the USB port and give it a go. First, run &lt;em&gt;nitrokey-app&lt;/em&gt; and set up the user and administrator passwords (if you have trouble finding the options, check out the menu bar).&lt;/p&gt;
&lt;p&gt;Now, play around with the key.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ p11tool --list-tokens
[...]

Token 2:
        URL: pkcs11:model=PKCS%2315%20emulated;manufacturer=ZeitControl;serial=xxxxxx;token=OpenPGP%20card%20%28User%20PIN%29
        Label: OpenPGP card (User PIN)
        Type: Hardware token
        Flags: RNG, Requires login
        Manufacturer: ZeitControl
        Model: PKCS#15 emulated
        Serial: 000500005c61
        Module: opensc-pkcs11.so


Token 3:
        URL: pkcs11:model=PKCS%2315%20emulated;manufacturer=ZeitControl;serial=xxxxxx;token=OpenPGP%20card%20%28User%20PIN%20%28sig%29%29
        Label: OpenPGP card (User PIN (sig))
        Type: Hardware token
        Flags: RNG, Requires login
        Manufacturer: ZeitControl
        Model: PKCS#15 emulated
        Serial: 000500005c61
        Module: opensc-pkcs11.so
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Remember this command, cause the URL field will be needed later.&lt;/p&gt;
&lt;h3&gt;Certificates&lt;/h3&gt;
&lt;p&gt;The login procedure involves checking PKCS12 certificates, so let's set up one using easy-rsa. First, create a file called "vars" in some directory, and put this inside:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;set_var EASYRSA_KEY_SIZE 4096
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, let's create the entire certificate chain for the user "thatsme". It will ask you for different passwords: one for the certificate authority (you only need to use it when adding another certificate), and one for the private key (you'll need it once, when uploading the private key to the smart card), so pay attention to them.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ln -s /usr/share/easy-rsa/3.0.8/ easyrsa
EASYRSA_VARS_FILE=`pwd`/vars ./easyrsa/easyrsa init-pki
EASYRSA_VARS_FILE=`pwd`/vars ./easyrsa/easyrsa build-ca
EASYRSA_VARS_FILE=`pwd`/vars ./easyrsa/easyrsa build-client-full thatsme
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whew, this wasn't as difficult as the last time I tried to set up a new certificate authority. Good job, easy-rsa folks!&lt;/p&gt;
&lt;p&gt;Now, store the generated private key and the corresponding certificate (approved by the newly minted certificate authority) into the smart card.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WARNING:&lt;/strong&gt; we generated a private key on the host computer. It can be intercepted by a rogue application on this computer, especially if you don't delete it fully (and deleting is harder than it seems). The interceptor will be able to follow the next steps to duplicate your smart card. If you want to be super careful, you should &lt;a href="https://github.com/OpenSC/OpenSC/wiki/Card-personalization"&gt;generate the key directly on the smart card&lt;/a&gt;, and use Certificate Signing Requests (CSRs) with easy-rsa to &lt;a href="https://wiki.archlinux.org/title/Easy-RSA#Sign_the_certificates_and_pass_them_back_to_the_server_and_clients"&gt;approve the key&lt;/a&gt;. This is not relevant to me, because I not will use the smart card to log in to any other computer. If someone snooped the key from the laptop, I'm already hosed. So I chose the easy way to generate the key.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pkcs15-init --delete-objects privkey,pubkey --id 3 --store-private-key ./pki/private/thatsme.key --format pem --auth-id 3 --verify-pin
pkcs15-init --id 3 --store-certificate ./pki/issued/thatsme.crt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See how snugly the key and the certificate both sit on your smart card now? Use the URL you noted earlier to show them.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ p11tool --provider /usr/lib64/opensc-pkcs11.so --list-all pkcs11:model=PKCS%2315%20emulated;manufacturer=ZeitControl;serial=xxxx;token=OpenPGP%20card%20%28User%20PIN%29
Object 0:
        URL: pkcs11:model=PKCS%2315%20emulated;manufacturer=ZeitControl;serial=xxxx;token=OpenPGP%20card%20%28User%20PIN%29;id=%03;object=Authentication%20key;type=public
        Type: Public key (RSA-4096)
        Label: Authentication key
        Flags: CKA_WRAP/UNWRAP; CKA_EXTRACTABLE; 
        ID: 03

Object 1:
        URL: pkcs11:model=PKCS%2315%20emulated;manufacturer=ZeitControl;serial=xxxx;token=OpenPGP%20card%20%28User%20PIN%29;id=%03;object=Cardholder%20certificate;type=cert
        Type: X.509 Certificate (RSA-4096)
        Expires: Sun Sep 28 09:07:36 2025
        Label: Cardholder certificate
        ID: 03
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I tried listing the private keys, but for some reason, p11tool won't display any.&lt;/p&gt;
&lt;h2&gt;Login&lt;/h2&gt;
&lt;p&gt;Now that the card is prepared, let's set up the login. First, copy the certificate authority to where sssd can find it. Any key signed with this certificate will be candidate for login.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo cp pki/ca.crt /etc/sssd/pki/sssd_auth_ca_db.pem
sudo chmod ugo-w /etc/sssd/pki/sssd_auth_ca_db.pem
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, set up sssd itself by creating the file &lt;code&gt;/etc/sssd/conf.d/base.conf&lt;/code&gt; with the following contents:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[sssd]
services = nss, pam
domains = files
disable_netlink = true

[domain/files]
id_provider = files

[pam]
pam_cert_auth = True
pam_p11_allowed_services = +kde, +kscreensaver, +sddm

[certmap/files/thatsme]
matchrule = &amp;lt;SUBJECT&amp;gt;^CN=thatsme$
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;NOTE:&lt;/em&gt; The common name (file name) given to easy-rsa must match the username on this system. If anyone knows how to break that relationship, please tell me! I know it has to do with &lt;a href="https://www.mankier.com/5/sss-certmap"&gt;sss-certmap&lt;/a&gt;, but the docs are incomprehensible to me.&lt;/p&gt;
&lt;p&gt;Finally, enable sssd. Do this as root:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl enable sssd
authselect select sssd with-smartcard --force
systemctl restart sssd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that's it. When the smart card is plugged in, you should be able to see this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo echo "ok"
PIN for OpenPGP card (User PIN):
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;rather than the normal&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo echo "ok"
Password: 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Similarly, the KDE screen locker will accept the smart card password rather than the user password. It doesn't indicate that this is happening though, other than being really slow with the card in.&lt;/p&gt;
&lt;h3&gt;Debugging&lt;/h3&gt;
&lt;p&gt;Enable logging by adding the &lt;code&gt;debug_level&lt;/code&gt; entry:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[pam]
pam_cert_auth = True
debug_level = 9
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and restarting sssd.&lt;/p&gt;
&lt;p&gt;The two relevant log files are here:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/var/log/sssd/sssd_pam.log&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;and&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/var/log/sssd/p11_child.log&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;but they are not that helpful, because sssd documentation doesn't really explain a lot.&lt;/p&gt;
&lt;p&gt;Good luck upgrading your opsec!&lt;/p&gt;
&lt;h2&gt;Actually, no login&lt;/h2&gt;
&lt;p&gt;I did all of that, I rebooted my computer, I plugged in the Nitrokey, and I entered my password at the SDDM login screen. The prompt takes some long seconds to resolve – good, it's working with the card.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Login refused&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Wait, what? I try again, same result. I try my regular password, and that goes through.&lt;/p&gt;
&lt;p&gt;The logs in /var/log/sssd/sssd_pam.log show:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[pam_reply] (0x4000): [CID#3] pam_reply initially called with result [9]: Authentication service cannot retrieve authentication info. this result might be changed during processing
[pam_reply] (0x0200): [CID#3] blen: 22
[pam_reply] (0x0200): [CID#3] Returning [9]: Authentication service cannot retrieve authentication info to the client
[client_recv] (0x0200): [CID#3] Client disconnected!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Meanwhile, a successful use in sudo:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[pam_reply] (0x4000): [CID#9] pam_reply initially called with result [9]: Authentication service cannot retrieve authentication info. this result might be changed during processing
[pam_reply] (0x0040): [CID#9] Assuming offline authentication setting status for pam call 249 to PAM_SUCCESS.
[pam_eval_prompting_config] (0x4000): [CID#9] No prompting configuration found.
[may_do_passkey_auth] (0x0400): [CID#9] Passkey auth not possible, SSSD built without passkey support!
[pam_reply] (0x0200): [CID#9] blen: 22
[pam_reply] (0x0200): [CID#9] Returning [0]: Success to the client
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I don't know if this difference is relevant. My search engine skills failed me, and I eventually gave up.&lt;/p&gt;
&lt;p&gt;I guess I'll just have to use the main password for login. Thankfully, when traveling, I resume the laptop from sleep, and the screen locker accepts the smart card password, so it's not that bad.&lt;/p&gt;
&lt;p&gt;Either way, if you know how to solve this problem (any sddm devs around?), please let me know! I'll update this post with your info.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Thanks to one of my coworkers at &lt;a href="https://puri.sm"&gt;Purism&lt;/a&gt;, for showing me that not every password must be guarded jealously.&lt;/p&gt;</description><author>dcz's posts</author><pubDate>Sun, 29 Oct 2023 16:00:00 GMT</pubDate><guid isPermaLink="true">https://dorotac.eu/posts/2fa_login/</guid></item><item><title>Career advice, 10 years in</title><link>https://ernest.oppet.it/2023/10/29/career-advice-10-years-in/</link><description>It just occurred to me that I&amp;#8217;ve been working professionally for 10 years 👴 Here are a few aphorisms and tidbits I&amp;#8217;ve picked up along the way, shared in the form of Kevin Kelly&amp;#8217;s. Your mileage may vary! Also sadly I&amp;#8217;ve found that the best way to internalise advice is to actually forget it when [&amp;#8230;]</description><author>Ernest Oppetit</author><pubDate>Sun, 29 Oct 2023 14:32:59 GMT</pubDate><guid isPermaLink="true">https://ernest.oppet.it/2023/10/29/career-advice-10-years-in/</guid></item><item><title>Note 120</title><link>https://qubyte.codes/notes/1698582606333</link><description>&lt;p&gt;I made a cheesecake the other day.&lt;/p&gt;

    &lt;img alt="A slice of white chocolate cheesecake on a small plate. A raspberry is perched atop the slice, and the handle of a fork can be seen behind the slice. The small plate is on a wooden table." src="https://qubyte.codes/images/1698582491759.jpeg" /&gt;</description><author>Qubyte Codes</author><pubDate>Sun, 29 Oct 2023 14:30:06 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/notes/1698582606333</guid></item><item><title>Light at the end of the tunnel</title><link>https://deadlime.hu/en/2023/10/29/light-at-the-end-of-the-tunnel/</link><description>Accessing your local machine from the Internet through SSH</description><author>deadlime</author><pubDate>Sun, 29 Oct 2023 12:13:38 GMT</pubDate><guid isPermaLink="true">https://deadlime.hu/en/2023/10/29/light-at-the-end-of-the-tunnel/</guid></item><item><title>Analyzing Data 170,000x Faster with Python</title><link>https://sidsite.com/posts/python-corrset-optimization/</link><description>The article, Analyzing Data 180,000x Faster with Rust, first presents some unoptimized Python code, and then shows the process of rewriting and optimizing the code in Rust, resulting in a 180,000x speed-up. The author notes:</description><author>sidsite</author><pubDate>Sun, 29 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://sidsite.com/posts/python-corrset-optimization/</guid></item><item><title>Daufuskie and Hunting Island 2023</title><link>https://solomon.io/daufuskie-hunting-island-2023/</link><description>A road trip through the South Carolina lowcountry.</description><author>Sam Solomon</author><pubDate>Sun, 29 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://solomon.io/daufuskie-hunting-island-2023/</guid></item><item><title>How to Get Total Views Statistic on Substack</title><link>https://chrisfrew.in/blog/how-to-get-total-views-statistic-on-substack/</link><description>A simple script gets the job done!</description><author>Chris' Full Stack Blog RSS Feed</author><pubDate>Sun, 29 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://chrisfrew.in/blog/how-to-get-total-views-statistic-on-substack/</guid></item><item><title>My Lolcow &amp;amp; Me</title><link>https://foreverliketh.is/blog/my-lolcow-me/</link><description>&lt;!--
Alonso Adventure
Lifelong joke
Make your own lols. We should be ok with being walking jokes. We're all lolcows.
Should I have abandoned him all together, dependance Been more sincere, that I envied his wild abandon, admired his thick skin.
--&gt;
&lt;details&gt;
Image Details &amp; Source
&lt;p&gt;&lt;a href="https://unsplash.com/photos/man-in-red-jacket-standing-beside-brown-cow-on-green-grass-field-during-daytime-74z1fatAwHo"&gt;man in red jacket standing beside brown cow on green grass field during daytime&lt;/a&gt; by &lt;a href="https://unsplash.com/@fraenkly"&gt;Frank Holleman&lt;/a&gt; on &lt;a href="https://unsplash.com/"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://foreverliketh.is/blog/my-lolcow-me/cow.jpg"&gt;Click Here for the Original, High-Quality Image&lt;/a&gt;&lt;/p&gt;
&lt;/details&gt;
&lt;hr /&gt;
&lt;p&gt;This page was last modified: &lt;strong&gt;October 31st, 2023&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h5 id="-well-ali-baba-had-them-forty-thieves-"&gt;🎶 Well, Ali Baba had them forty thieves 🎶 &lt;a class="anchor" href="#-well-ali-baba-had-them-forty-thieves-"&gt;#&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;Back in my college days, towards the start, I made an acquaintance that I&amp;rsquo;ll never forget. Over time, I&amp;rsquo;d grow to know him. I want to imagine that there was a period in that relationship where we would have sincerely considered one another &amp;ldquo;friend&amp;rdquo;. For the longest time, I certainly would have &lt;em&gt;&lt;strong&gt;called&lt;/strong&gt;&lt;/em&gt; him that. But, now, years later, I sit here at my keyboard with the realization that what I &lt;em&gt;&lt;strong&gt;actually&lt;/strong&gt;&lt;/em&gt; treated him like, was a &lt;a href="https://www.urbandictionary.com/define.php?term=Lolcow"&gt;&amp;ldquo;Lolcow&amp;rdquo;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not using that word lightly either. I&amp;rsquo;m well aware of its connotations online. I link specifically to that &lt;a href="https://en.wikipedia.org/wiki/Urban_Dictionary"&gt;Urban Dictionary&lt;/a&gt; definition because it excludes the elements of harassment, stalking, bullying and more, associated with the term. Sadly, even without those aspects, it&amp;rsquo;s still not a pretty picture. The dynamic is about extracting (&amp;ldquo;milking&amp;rdquo;) comedy (&amp;ldquo;lols&amp;rdquo;) from a human being (&amp;ldquo;a cow&amp;rdquo;). Almost undoubtedly at &lt;em&gt;&lt;strong&gt;their&lt;/strong&gt;&lt;/em&gt; expense, and irrespective of their awareness of it.&lt;/p&gt;
&lt;p&gt;Right now, online, there&amp;rsquo;s a bit of a mainstream-adjacent zeitgeist skirting around the label and the individuals, and groups, (unfortunate enough to be) caught within its orbit. While I reckon this growth in popularity is primarily a consequence of the paradoxically unquenchable thirst for &amp;ldquo;content&amp;rdquo; that our oversaturated, ad-centered, corporatized, modern internet possesses. I also think there&amp;rsquo;s a little more to it.&lt;/p&gt;
&lt;h5 id="-scheherazade-had-a-thousand-tales-"&gt;🎶 Scheherazade had a thousand tales 🎶 &lt;a class="anchor" href="#-scheherazade-had-a-thousand-tales-"&gt;#&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;Alonso was&amp;hellip; really something else, yo. From joining cults and renouncing loving family members to running with gangs and taking first dates on creepy, orgy-esque outings. My most insane life stories feature Alonso. And at some moment in our time together, I began seeing him as less of a person and more of a generator of wild, comical experiences. The decisions I&amp;rsquo;d make regarding him warped to the degree of prioritizing concepts like &amp;ldquo;entertainment potential&amp;rdquo; over more sensible things like &amp;ldquo;personal wellbeing&amp;rdquo; or &amp;ldquo;troublesome inconvenience&amp;rdquo;. Take the cult story for example: &lt;em&gt;&lt;strong&gt;I drove him to the meetings!&lt;/strong&gt;&lt;/em&gt; Cause it was &lt;em&gt;&lt;strong&gt;funny.&lt;/strong&gt;&lt;/em&gt; It felt &lt;a href="https://en.wikipedia.org/wiki/Sitcom"&gt;sitcom&lt;/a&gt;-ish.&lt;/p&gt;
&lt;p&gt;But it didn&amp;rsquo;t start out that way&amp;hellip; And while I&amp;rsquo;m not going to excuse or justify my actions, I didn&amp;rsquo;t change without reason. Alonso &lt;em&gt;&lt;strong&gt;constantly&lt;/strong&gt;&lt;/em&gt; made senseless choices. Well-meaning intent, timely interventions, deep discussions, seemingly transformative revelations, it didn&amp;rsquo;t matter. He&amp;rsquo;d pick the &amp;ldquo;wrong&amp;rdquo; option, and then, even more confounding was, he could not &lt;em&gt;&lt;strong&gt;reconcile&lt;/strong&gt;&lt;/em&gt; their (&lt;em&gt;usually&lt;/em&gt;) logical outcomes. To a cartoonish degree! While his real friends grew increasingly frustrated with him, I just kinda &lt;a href="https://www.urbandictionary.com/define.php?term=Checked%20out"&gt;checked-out&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Ironically, it was that very apathy that led us to hanging out the most we ever would. Was it because I stopped treating him like a &amp;ldquo;problem&amp;rdquo; needing to be &amp;ldquo;fixed&amp;rdquo; and it allowed us to grow closer? No, dear reader, I&amp;rsquo;m pretty sure he just enjoyed having an overt enabler. And, so&amp;hellip; there we were: my ignorant, home-brew Don Quixote, and I, his twisted, off-brand Sancho Panza. Doing whatever I could to oblige the &lt;a href="https://en.wiktionary.org/wiki/tilt_at_windmills"&gt;fighting of windmills&lt;/a&gt;. Chapter by chapter, off we went, filling the pages of our lowly &amp;ldquo;epic&amp;rdquo;.&lt;/p&gt;
&lt;h5 id="-but-master-you-in-luck-cause-up-your-sleeves-"&gt;🎶 But, master, you in luck &amp;lsquo;cause, up your sleeves 🎶 &lt;a class="anchor" href="#-but-master-you-in-luck-cause-up-your-sleeves-"&gt;#&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;What made me that way? What was the attraction? What kept me fascinated? I&amp;rsquo;m no angel, but it&amp;rsquo;d be superficial to just write me off as a &amp;ldquo;heartless, manipulative jerk&amp;rdquo;. Likewise, it would be just as shallow to place the blame squarely on Alonso and his &lt;a href="https://en.wikipedia.org/wiki/Village_idiot"&gt;village idiot&lt;/a&gt;&amp;ndash;like behavior. Here&amp;rsquo;s what I suspect:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://knowledge.uchicago.edu/record/3996"&gt;Morbid curiosity might be evolutionarily ingrained in us&lt;/a&gt;. Left to its own devices, traffic &lt;em&gt;&lt;strong&gt;WILL slow&lt;/strong&gt;&lt;/em&gt; for a &lt;a href="https://en.wiktionary.org/wiki/train_wreck"&gt;train wreck&lt;/a&gt;. &lt;a href="https://www.vox.com/the-highlight/23596969/bad-news-negativity-bias-media"&gt;&amp;ldquo;The News&amp;rdquo; &lt;em&gt;&lt;strong&gt;IS&lt;/strong&gt;&lt;/em&gt; biased toward the negative&lt;/a&gt;. Surely you&amp;rsquo;re familiar with &lt;a href="https://en.wikipedia.org/wiki/Freak_show"&gt;freak shows&lt;/a&gt;, traveler? The fascination with the unusual, isn&amp;rsquo;t so unusual. But I&amp;rsquo;d say this still only addresses about &lt;em&gt;&lt;strong&gt;half&lt;/strong&gt;&lt;/em&gt; of the &amp;ldquo;lolcow phenomena&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.scientificamerican.com/article/whats-so-funny-the-science-of-why-we-laugh/"&gt;The science of laughter&lt;/a&gt; is surprisingly vast, nuanced and unfinished. But as far back as Ancient Greece there have existed &lt;a href="https://jonathansandling.com/plato-on-humour-and-laughter/"&gt;theories linking humor and superiority&lt;/a&gt;. And while it&amp;rsquo;s obvious that there&amp;rsquo;s more at play here, my personal feelings on the matter hint at this being a significant element.&lt;/p&gt;
&lt;h5 id="-you-got-a-brand-of-magic-never-fails-"&gt;🎶 You got a brand of magic never fails 🎶 &lt;a class="anchor" href="#-you-got-a-brand-of-magic-never-fails-"&gt;#&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;In my opinion, Alonso rarely cared about anything but himself and our time together was nothing short of reciprocal &lt;a href="https://en.wikipedia.org/wiki/Parasitism"&gt;parasitism&lt;/a&gt;. But he never needed me as much as I needed him. I used him to feel good about myself. I used him as a key to wacky, dreamlike affairs. And I used him as a vicarious substitute for failure, to &lt;a href="https://quoteinvestigator.com/2015/01/09/say-nothing/"&gt;avoid the criticism&lt;/a&gt; that I both hypocritically dished him and secretly, childishly feared.&lt;/p&gt;
&lt;p&gt;Alonso was a fool, in more ways than one. But &lt;a href="https://en.wikipedia.org/wiki/Wise_fool"&gt;even fools bear wisdom&lt;/a&gt;. He was eternally blind (and deaf) to it mind you, but it was there. Unbelievable were the moments where things just&amp;hellip; &lt;em&gt;&lt;strong&gt;worked out&lt;/strong&gt;&lt;/em&gt; for him. Frequent too, beyond that of &lt;a href="https://en.wiktionary.org/wiki/a_stopped_clock_is_right_twice_a_day"&gt;a broken clock&lt;/a&gt;. A &amp;ldquo;man of action&amp;rdquo;, and that&amp;rsquo;s not meant to be a compliment; &lt;em&gt;&lt;strong&gt;if&lt;/strong&gt;&lt;/em&gt; he had thoughts I can practically guarantee they were worthless. But &lt;a href="https://en.wiktionary.org/wiki/fortune_favors_the_bold"&gt;fortune favors the bold&lt;/a&gt;, and it never audits the source.&lt;/p&gt;
&lt;p&gt;That walking, talking joke&amp;hellip; That absolute circus clown of a man! For all his faults, he still, &lt;em&gt;somehow&lt;/em&gt;, managed to embody a valuable truth. One more priceless than even the lifetime of laughs he left me with:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;To live is to lol.&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;So, visitor, I ask you, truly&amp;ndash;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Got_Milk%3F"&gt;got milk?&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Contributors: &lt;a href="https://en.wikipedia.org/wiki/Plato"&gt;Plato&lt;/a&gt; | &lt;a href="https://en.wikipedia.org/wiki/Miguel_de_Cervantes"&gt;Miguel de Cervantes&lt;/a&gt; &amp;ndash;&amp;gt; Don Quixote [&lt;a href="https://en.wikipedia.org/wiki/Don_Quixote"&gt;#1&lt;/a&gt; &amp;amp; &lt;a href="https://standardebooks.org/ebooks/miguel-de-cervantes-saavedra/don-quixote/john-ormsby"&gt;#2&lt;/a&gt;] | &lt;a href="https://en.wikipedia.org/wiki/One_Thousand_and_One_Nights"&gt;One Thousand and One Nights&lt;/a&gt; &amp;ndash;&amp;gt; &lt;a href="https://en.wikipedia.org/wiki/Aladdin"&gt;Aladdin&lt;/a&gt; &amp;ndash;&amp;gt; &lt;a href="https://en.wikipedia.org/wiki/Aladdin_(1992_Disney_film)"&gt;Aladdin (1992)&lt;/a&gt; &amp;ndash;&amp;gt; &lt;a href="https://en.wikipedia.org/wiki/Robin_Williams"&gt;Robin Williams&lt;/a&gt; &amp;ndash;&amp;gt; Friend Like Me [&lt;a href="https://en.wikipedia.org/wiki/Friend_Like_Me"&gt;#1&lt;/a&gt; &amp;amp; &lt;a href="https://youtu.be/Qx91ff77yzM"&gt;#2&lt;/a&gt;] &amp;ndash;&amp;gt; &lt;a href="https://en.wikipedia.org/wiki/Ali_Baba_and_the_Forty_Thieves"&gt;Ali Baba and the Forty Thieves&lt;/a&gt; &amp;ndash;&amp;gt; &lt;a href="https://en.wikipedia.org/wiki/Scheherazade"&gt;Scheherazade&lt;/a&gt; | &lt;a href="https://en.wikipedia.org/wiki/Elbert_Hubbard"&gt;Elbert Hubbard&lt;/a&gt; &amp;ndash;&amp;gt; &lt;a href="https://babel.hathitrust.org/cgi/pt?id=hvd.hx4zk6&amp;amp;seq=11"&gt;Little journeys to the homes of American statesmen&lt;/a&gt; | &lt;a href="https://www.coltanscrivner.com/"&gt;Coltan Scrivner&lt;/a&gt; | &lt;a href="https://jonathansandling.com/"&gt;Jonathan Sandling&lt;/a&gt; | &lt;a href="https://en.wikipedia.org/wiki/Vox_(website)"&gt;Vox&lt;/a&gt; &amp;ndash;&amp;gt; &lt;a href="https://en.wikipedia.org/wiki/Dylan_Matthews"&gt;Dylan Matthews&lt;/a&gt; | &lt;a href="https://en.wikipedia.org/wiki/Scientific_American"&gt;Scientific American&lt;/a&gt; &amp;ndash;&amp;gt; &lt;a href="https://www.scientificamerican.com/author/giovanni-sabato/"&gt;Giovanni Sabato&lt;/a&gt; | &lt;a href="https://www.youtube.com/@genosamuel2.1"&gt;Geno Samuel&lt;/a&gt; | &lt;a href="https://en.wikipedia.org/wiki/Hiromu_Arakawa"&gt;Hiromu Arakawa&lt;/a&gt; &amp;ndash;&amp;gt; &lt;a href="https://en.wikipedia.org/wiki/Fullmetal_Alchemist"&gt;Fullmetal Alchemist&lt;/a&gt; &amp;ndash;&amp;gt; &lt;a href="https://tvtropes.org/pmwiki/pmwiki.php/Main/EquivalentExchange"&gt;Equivalent Exchange&lt;/a&gt; | &lt;a href="https://en.wikipedia.org/wiki/Akira_Toriyama"&gt;Akira Toriyama&lt;/a&gt; &amp;ndash;&amp;gt; &lt;a href="https://en.wikipedia.org/wiki/Dragon_Ball_Z"&gt;Dragon Ball Z&lt;/a&gt; &amp;ndash;&amp;gt; &lt;a href="https://foreverliketh.is/docs/yang/negative-pride/"&gt;Vegeta&amp;rsquo;s Pride&lt;/a&gt; | &lt;a href="https://en.wikipedia.org/wiki/Trey_Parker"&gt;Trey Parker&lt;/a&gt; &amp;amp; &lt;a href="https://en.wikipedia.org/wiki/Matt_Stone"&gt;Matt Stone&lt;/a&gt; &amp;ndash;&amp;gt; &lt;a href="https://en.wikipedia.org/wiki/South_Park"&gt;South Park&lt;/a&gt; &amp;ndash;&amp;gt; &lt;a href="https://en.wikipedia.org/wiki/Britney%27s_New_Look"&gt;Britney&amp;rsquo;s New Look&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://indieweb.org/syndication-models"&gt;Syndication&lt;/a&gt;: &lt;a href="https://forum.agoraroad.com/index.php?threads/agora-road-travelogue-october-blogging.6113/post-91175"&gt;Agora Road&lt;/a&gt;, &lt;a href="https://indieweb.social/@accordionpolar/111320588776876293"&gt;Mastodon&lt;/a&gt; &amp;amp; &lt;a href="https://basementcommunity.com/threads/147?postID=1622#1622"&gt;basement community&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This blog post is part of Agora Road Travelogue - Oct [&lt;a href="https://forum.agoraroad.com/index.php?threads/agora-road-travelogue-october-blogging.6113/"&gt;#1&lt;/a&gt; &amp;amp; #2]. A project attempting to encourage individuals to both write and exercise ownership over their content.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id="comments"&gt;Comments &lt;a class="anchor" href="#comments"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Email: &lt;a href="mailto:hi@foreverliketh.is"&gt;hi@foreverliketh.is&lt;/a&gt;&lt;/p&gt;

&lt;div id="fastcomments-widget"&gt;&lt;/div&gt;

&lt;details&gt;
IndieWeb h-entry
&lt;article class="h-entry"&gt;
&lt;h1 class="p-name"&gt;My Lolcow &amp; Me&lt;/h1&gt;
&lt;p&gt;Published by &lt;a class="p-author h-card" href="https://foreverliketh.is/"&gt;foreverliketh.is&lt;/a&gt;
on &lt;time class="dt-published" datetime="2023-10-29 10:00:00"&gt;29&lt;sup&gt;th&lt;/sup&gt; October 2023&lt;/time&gt;&lt;/p&gt;
&lt;p class="p-summary"&gt;Equivalent Exchange&lt;/p&gt;
&lt;div class="e-content"&gt;
&lt;p&gt;🎶 Well, Ali Baba had them forty thieves 🎶&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;a class="u-url" href="https://foreverliketh.is/blog/my-kind-of-weather/"&gt;My Lolcow &amp; Me&lt;/a&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;a class="u-syndication" href="https://forum.agoraroad.com/index.php?threads/agora-road-travelogue-october-blogging.6113/post-91175" rel="syndication"&gt;Agora Road&lt;/a&gt;,
&lt;a class="u-syndication" href="https://indieweb.social/@accordionpolar/111320588776876293" rel="syndication"&gt;Mastodon&lt;/a&gt; &amp;
&lt;a class="u-syndication" href="https://basementcommunity.com/threads/147?postID=1622#1622" rel="syndication"&gt;basement community&lt;/a&gt;
&lt;/div&gt;
&lt;a class="p-category" href="https://foreverliketh.is/categories/psychology/"&gt;Psychology&lt;/a&gt;
&lt;/article&gt;
&lt;/details&gt;</description><author>foreverliketh.is</author><pubDate>Sun, 29 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://foreverliketh.is/blog/my-lolcow-me/</guid></item><item><title>Knowing Your Workflow Tools: pnpm-only</title><link>https://medium.com/full-stack-architecture/knowing-your-workflow-tools-pnpm-only-264bef193d0a?source=rss----7ddf416a7f74---4</link><description>&lt;p&gt;Assure &lt;a href="https://pnpm.io/"&gt;pnpm&lt;/a&gt; is the only package manager used in your node projects.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/630/1*dQKV432GoSrPJBwnRUePOg.png" /&gt;&lt;figcaption&gt;Photo by &lt;a href="https://unsplash.com/@alvaroserrano"&gt;Álvaro Serrano&lt;/a&gt;.&lt;/figcaption&gt;&lt;/figure&gt;&lt;h3&gt;The Opinionated TLDR&lt;/h3&gt;&lt;p&gt;The &lt;a href="https://github.com/erichosick/pnpm-only-template"&gt;pnpm-only-template&lt;/a&gt; repository on GitHub includes a &lt;a href="https://github.com/erichosick/pnpm-only-template/blob/main/pnpm-only.sh"&gt;shell script&lt;/a&gt; that configures your node project to exclusively use &lt;a href="https://pnpm.io/"&gt;pnpm&lt;/a&gt; as its package manager. Also consider:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Mastering the tools you employ in software development.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Favor&lt;/strong&gt; deep expertise in a focused set of tools &lt;strong&gt;over&lt;/strong&gt; a cursory understanding of many.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Favor&lt;/strong&gt; constraining the workflow &lt;strong&gt;over&lt;/strong&gt; documentation.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Introduction&lt;/h3&gt;&lt;p&gt;I enjoy coding and software architecture, but dealing with the tools that facilitate that? Not so much. I used to get by knowing just the basics of each tool; just enough to “get the job done.”&lt;/p&gt;&lt;p&gt;However, I’ve learned much too late, that to have an effective workflow to do the things I like, I need to really understand the tools I use.&lt;/p&gt;&lt;p&gt;Taking a step back, it’s been a transformative experience to deep-dive into the tools that shape my coding process, starting with setting up and configuring node packages. The package manager I use for node projects is &lt;a href="https://pnpm.io/"&gt;pnpm&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Assuring that only pnpm is used as the package manger for a given project goes beyond just documentation. Constraining the workflow itself is a more effective way to guide new developers, reducing the likelihood of them accidentally using the wrong package manager.&lt;/p&gt;&lt;h3&gt;Enforcing pnpm as the Exclusive Package Manager&lt;/h3&gt;&lt;p&gt;Ensuring that pnpm is the sole package manager for our projects required some research and community contributions. Special thanks to those who have shared insights on this topic.&lt;/p&gt;&lt;h4&gt;Disabling yarn&lt;/h4&gt;&lt;p&gt;Disabling yarn as a package installer was easiest.&lt;/p&gt;&lt;p&gt;Running &lt;a href="https://github.com/pnpm/only-allow"&gt;only-allow&lt;/a&gt; in the preinstall script stops yarn, yarn add and yarn install: even with an un-initialized project. The results can be seen below.&lt;/p&gt;&lt;pre&gt;$ yarn add {package}, yarn install or yarn&lt;br /&gt;yarn add v1.22.19&lt;br /&gt;info No lockfile found.&lt;br /&gt;$ npx only-allow pnpm&lt;br /&gt;╔═════════════════════════════════════════════════════════════╗&lt;br /&gt;║                                                             ║&lt;br /&gt;║   Use &amp;quot;pnpm install&amp;quot; for installation in this project.      ║&lt;br /&gt;║                                                             ║&lt;br /&gt;║   If you don't have pnpm, install it via &amp;quot;npm i -g pnpm&amp;quot;.   ║&lt;br /&gt;║   For more details, go to https://pnpm.js.org/              ║&lt;br /&gt;║                                                             ║&lt;br /&gt;╚═════════════════════════════════════════════════════════════╝&lt;br /&gt;error Command failed with exit code 1.&lt;br /&gt;info Visit https://yarnpkg.com/en/docs/cli/add for documentation&lt;br /&gt;about this command.&lt;/pre&gt;&lt;h4&gt;Disabling npm&lt;/h4&gt;&lt;p&gt;It takes a little more work to disable npm from installing packages because npm installs packages before calling the preinstall script.&lt;/p&gt;&lt;p&gt;To stop npm from installing packages, we needed to add to package.json:&lt;/p&gt;&lt;pre&gt;// package.json&lt;br /&gt;{&lt;br /&gt;  &amp;quot;engines&amp;quot;: {&lt;br /&gt;    &amp;quot;npm&amp;quot;: &amp;quot;Use pnpm instead of npm.&amp;quot;&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;&lt;p&gt;and create a .npmrc files with engine-strict=true:&lt;/p&gt;&lt;pre&gt;echo &amp;quot;# Stop npm from being used as the installer.&lt;br /&gt;# The following was added to package.json&lt;br /&gt;# { \&amp;quot;engines\&amp;quot;: { \&amp;quot;npm\&amp;quot;: \&amp;quot;Use pnpm instead of npm.\&amp;quot; } }&lt;br /&gt;engine-strict=true&amp;quot; &amp;gt; .npmrc&lt;/pre&gt;&lt;p&gt;Running npm install and npm add now shows the following error:&lt;/p&gt;&lt;pre&gt;$ npm install&lt;br /&gt;npm ERR! code EBADENGINE&lt;br /&gt;npm ERR! engine Unsupported engine&lt;/pre&gt;&lt;p&gt;All of this is done for you using the following &lt;a href="https://github.com/erichosick/pnpm-only-template/blob/main/pnpm-only.sh"&gt;shell script&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;What Sort of Works and Other Options&lt;/h3&gt;&lt;h4&gt;Pnpm Used to Initialize a Project&lt;/h4&gt;&lt;p&gt;If a project has already been initialized by running pnpm install, running npm results in an error:&lt;/p&gt;&lt;pre&gt;$ npm install&lt;br /&gt;npm ERR! Cannot set properties of null (setting 'peer')&lt;br /&gt;npm ERR! A complete log of this run can be found in: ....&lt;/pre&gt;&lt;h4&gt;Remove npm (not always possible)&lt;/h4&gt;&lt;p&gt;Removing npm from the developers machine is an option.&lt;/p&gt;&lt;h4&gt;Script a Replacement (not recommended)&lt;/h4&gt;&lt;pre&gt;# create the file&lt;br /&gt;&lt;br /&gt;echo -e '#!/bin/bash&lt;br /&gt;echo &amp;quot;Please use pnpm.&amp;quot;&lt;br /&gt;exit 1' &amp;gt; npm &amp;amp;&amp;amp; chmod +x npm&lt;br /&gt;&lt;br /&gt;# you can run it as follows but calling&lt;br /&gt;&lt;br /&gt;$ ./npm&lt;br /&gt;&lt;br /&gt;# this still runs npm&lt;br /&gt;$ npm&lt;br /&gt;&lt;br /&gt;# WARNING: Adding the current directory as part of the PATH can&lt;br /&gt;# be dangerous.&lt;br /&gt;# prepend the current working directory to your path&lt;br /&gt;# MUST be prepended so the npm script will override npm&lt;br /&gt;echo 'export PATH=&amp;quot;.:$PATH&amp;quot;' &amp;gt;&amp;gt; ~/.zshrc &amp;amp;&amp;amp; source ~/.zshrc&lt;/pre&gt;&lt;h3&gt;What Doesn’t Work&lt;/h3&gt;&lt;h4&gt;Using Package.json packageManager Option&lt;/h4&gt;&lt;p&gt;At the time of writing, there is an experimental &lt;a href="https://nodejs.org/api/packages.html#packagemanager"&gt;packageManager&lt;/a&gt; option in package.json which doesn't seem to work yet.&lt;/p&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;Mastering your workflow tools is not just a nicety; it’s a necessity for efficient and effective software development. While documentation has its place, favor a workflow that’s designed to guide developers in the right direction. This is what the &lt;a href="https://github.com/erichosick/pnpm-only-template#disabling-yarn"&gt;pnpm-only-template&lt;/a&gt; aims to achieve.&lt;/p&gt;&lt;img alt="" height="1" src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=264bef193d0a" width="1" /&gt;&lt;hr /&gt;&lt;p&gt;&lt;a href="https://medium.com/full-stack-architecture/knowing-your-workflow-tools-pnpm-only-264bef193d0a"&gt;Knowing Your Workflow Tools: pnpm-only&lt;/a&gt; was originally published in &lt;a href="https://medium.com/full-stack-architecture"&gt;The Opinionated Software Architect&lt;/a&gt; on Medium, where people are continuing the conversation by highlighting and responding to this story.&lt;/p&gt;</description><author>The Opinionated Software Architect - Medium</author><pubDate>Sun, 29 Oct 2023 00:38:56 GMT</pubDate><guid isPermaLink="true">https://medium.com/full-stack-architecture/knowing-your-workflow-tools-pnpm-only-264bef193d0a?source=rss----7ddf416a7f74---4</guid></item><item><title>HN500 Updates</title><link>https://brntn.me/blog/hn500-updates/</link><description>&lt;p&gt;&lt;a href="https://hn500.brntn.me"&gt;HN500&lt;/a&gt; is a tiny site that lists posts on Hacker News that receive over 500 points. Unfortunately, it was down for a few days this week. I migrated servers and had a mistake in the cronjob that fetches posts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fixed now!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The good news is that I think it's only really used by me (of course, there are no analytics, and I'm keeping it up &lt;em&gt;for me&lt;/em&gt;, so I don't mind not knowing).&lt;/p&gt;
&lt;p&gt;There are two features that I've added in the last few months that might be valuable to folks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://hn500.brntn.me/summaries/"&gt;/summaries/&lt;/a&gt; shows a summary of all posts from the last 7 days. The summaries are generated using the &lt;a href="https://help.kagi.com/kagi/api/summarizer.html"&gt;Kagi Universal Summarizer&lt;/a&gt; API.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://hn500.brntn.me/feed.json"&gt;JSON Feed&lt;/a&gt; includes the summary and has been updated with better formatting for the story/comments links.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I rarely visit the HN500 homepage now and instead use the feed as a way to quickly catch up on the popular stories through my RSS reader.&lt;/p&gt;</description><author>Brenton Cleeland</author><pubDate>Sat, 28 Oct 2023 22:49:31 GMT</pubDate><guid isPermaLink="true">https://brntn.me/blog/hn500-updates/</guid></item><item><title>[Computing] Expect-CT Lite: A humble proposal for minimal CT enforcement in TLS certificates</title><link>https://www.devever.net/~hl/expect-ct-lite</link><description>&lt;p&gt;Last week we witnessed the &lt;a href="xmpp-incident"&gt;discovery of the compromise of an XMPP
service&lt;/a&gt;, most likely by a Western nation-state. While it's &lt;a href="xmpp-incident-2#context-of-certificate-transparency"&gt;not
directly related&lt;/a&gt; to any
attack which was carried out in this incident, one thing that has been brought
to my attention in the aftermath of this is that Certificate Transparency
logging remains optional for CAs.&lt;/p&gt;</description><author>devever.net/~hl</author><pubDate>Sat, 28 Oct 2023 17:07:00 GMT</pubDate><guid isPermaLink="true">https://www.devever.net/~hl/expect-ct-lite</guid></item><item><title>[Computing] Mitigating the Hetzner/Linode XMPP.ru MitM interception incident, part 2: XMPP-specific mitigations</title><link>https://www.devever.net/~hl/xmpp-incident-2</link><description>&lt;p&gt;I've had a few comments in response to my previous article, &lt;a href="https://www.devever.net/~hl/xmpp-incident"&gt;Mitigating the
Hetzner/Linode XMPP.ru MitM interception
incident&lt;/a&gt; which have provided
XMPP-specific suggestions for how this could have been mitigated.&lt;/p&gt;</description><author>devever.net/~hl</author><pubDate>Sat, 28 Oct 2023 17:06:00 GMT</pubDate><guid isPermaLink="true">https://www.devever.net/~hl/xmpp-incident-2</guid></item><item><title>Practical Deep Learning</title><link>https://vit.baisa.cz/notes/learn/practical-deep-learning/</link><description>notes from the course by fast.ai</description><author>Vít Baisa</author><pubDate>Sat, 28 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://vit.baisa.cz/notes/learn/practical-deep-learning/</guid></item><item><title>Hyperdimension Neptunia Re;Birth1 review</title><link>https://burakku.com/blog/hyperdimension-neptunia-rebirth1-review/</link><description>&lt;p&gt;&lt;img alt="Hyperdimension Neptunia Re;Birth1" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;I can't believe it's not kusoge!&lt;/p&gt;
&lt;p&gt;The story of Hyperdimension Neptunia Re;Birth1 isn't really anything special. It's ultimately just kind of a generic save-the-world adventure story and the actual substance is in the (comedic) character interactions more than anything else. I don't think I can claim to have loved the story, but I did find it charming in its own way. At least I don't regret reading through it. The English translation seems a bit liberal, but not to a degree that it'd completely ruin the entire thing either.&lt;/p&gt;
&lt;p&gt;The combat is surprisingly good. There's kind of a good rhythm to the whole thing, where you first try to get a sneak attack in the overworld, then take turns in the battle to position yourself optimally, chip away at the enemy's shields and then deal heavy damage to them. During boss fights you would also look to optimise the mileage you get out of your powerful EX skills too. It's also fairly fast-paced, with the possibility to skip most of the animations, so you can really charge through your battles.&lt;/p&gt;
&lt;p&gt;And boy is it a good thing the combat feels as good as it does since this game leans very heavily on grinding to pad out the game. I don't think there's any way to get through the story without grinding. You just won't get the levels to clear all the level check bosses. I had trouble beating a couple of the bosses, both early in the game and late in the game, and when I searched for tips in case I missed something, most of the advice on the Internet was "grind more". So if there's a way to beat the game without grinding, it went unnoticed by me and seemingly the rest of the Internet too.&lt;/p&gt;
&lt;p&gt;The grinding also extends to the achievements. About half of the achievements for this game require grinding, be it for character levels or money. The amount of grinding is pretty mental. Really has this "low budget, must stretch out the game somehow" thing written all over it.&lt;/p&gt;
&lt;p&gt;Not to say that there's no aspect for battle tactics and gameplay. For example, I lost to a boss, restarted the fight and then won the second bout. No grinding needed in the between the attempts. However, I'm not sure if the victory game down to skills or just luck. I feel like there's a limited amount of things you can do in regards to your tactics when the boss has the ability to insta-kill two members from your three-member party. It's quite possible that the boss just unleashed less of its wave of death area-of-effect attacks on my party on the latter attempt.&lt;/p&gt;
&lt;p&gt;The rest of the game mechanics are kinda lame. The quests feel like they were added to check a checkbox, same as with the colosseum. And the share balancing act that you need to do in order to unlock the true ending or to unlock the little sisters is also not amazing, especially since it'll involve those quests and colosseum. Not massively time consuming thankfully, but still a bit of a mindless grind, doing the same things again and again to shift some percentages around.&lt;/p&gt;
&lt;p&gt;Technically the game isn't the best. While I do like the art, despite the fact that the game reuses assets like it's going out of fashion, the graphics felt so bad and blurry at the beginning that I felt the need to install the &lt;a href="https://github.com/tlaik/neptastic/"&gt;Neptastic Mod&lt;/a&gt; on my Steam Deck. That did certainly help the game look nicer, although the FPS would not necessarily always be a stable 60 FPS. It also felt like the FPS would gradually get worse as I kept the game open as the Steam Deck was asleep. I think I went from around 40 FPS to 60 FPS in the same dungeon by just simply restarting the game. Deck users beware.&lt;/p&gt;
&lt;p&gt;But in general, Hyperdimension Neptunia Re;Birth1 does run pretty well on a Steam Deck. Maybe not as well as you'd expect considering that this is originally a PlayStation Vita game, but well enough even with the modding. Combined with the fast-paced combat and heavy focus on grinding, it makes for a pretty decent portable time killer when you have a couple of minutes to do something. Not really sure if I'd ever want to actually sit in front of my gaming PC and play through this game. It just doesn't feel like that kind of a game (quite possibly due to its Vita roots), which is why I played it from start to finish on the Steam Deck. Fairly certain even that I beat most of the game while sitting on the toilet.&lt;/p&gt;
&lt;p&gt;Hyperdimension Neptunia Re;Birth1 is a bit of a messy game with good core gameplay loop and enough charm to keep you around. For the couple of euro I paid for it, it did make for some good portable entertainment. Just don't really expect much beyond that from it.&lt;/p&gt;</description><author>ブラック</author><pubDate>Sat, 28 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/hyperdimension-neptunia-rebirth1-review/</guid></item><item><title>ActionMailer attachments in Ruby on Rails</title><link>https://railsnotes.xyz/blog/rails-actionmailer-attachments</link><description>ActionMailer makes it easy to attach files to your emails. In this article, I show you how to attach single or multiple files, set custom encodings and mime_types, and attach images as inline attachments to display in your email body.</description><author>RailsNotes, the Ruby on Rails guides you wished you had.</author><pubDate>Sat, 28 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://railsnotes.xyz/blog/rails-actionmailer-attachments</guid></item><item><title>Serverless, GPU Accelerated OpenAI API Using Podman, Llama.cpp, and systemd - No OpenFaaS Here!</title><link>https://blog.maxg.io/serverless-localai/</link><description>&lt;p&gt;Recently, I began putting together a new personal server for large language model training and inference. I really was interested in experimenting more with llama models and wanted the faster iterations having a local development machine would bring. &lt;/p&gt;&lt;p&gt;&lt;strong&gt;TLDR: &lt;a href="https://gist.github.com/mgolub2/4f255aba09e186945bc093d6189ae497?ref=blog.maxg.io"&gt;Script here&lt;/a&gt; that creates a podman container, systemd service, and socket&lt;/strong&gt;&lt;/p&gt;</description><author>9μm Pixels</author><pubDate>Fri, 27 Oct 2023 20:45:24 GMT</pubDate><guid isPermaLink="true">https://blog.maxg.io/serverless-localai/</guid></item><item><title>The Attention Mal-Economy and My Brush with Terence Tao</title><link>https://synystron.substack.com/p/the-attention-mal-economy-and-my</link><description>I've been shocked, repeatedly, now in 2023 with how darn hard it is, anymore, to get any kind of praise (or even engagement) from others anymore, online.</description><author>Synystron Synlogica</author><pubDate>Fri, 27 Oct 2023 19:54:21 GMT</pubDate><guid isPermaLink="true">https://synystron.substack.com/p/the-attention-mal-economy-and-my</guid></item><item><title>2023-10-27-001</title><link>https://srijan.ch/notes/2023-10-27-001</link><description>#Patterns</description><author>Srijan Choudhary, all posts</author><pubDate>Fri, 27 Oct 2023 19:05:00 GMT</pubDate><guid isPermaLink="true">https://srijan.ch/notes/2023-10-27-001</guid></item><item><title>Sofa consensus</title><link>https://rybakov.com/blog/sofa/</link><description>&lt;figure class="full"&gt;



&lt;img alt="" class="lazyload" src="../images/hiq/sofa_SDIM4468.jpg" /&gt;




&lt;/figure&gt;
&lt;br /&gt;
&lt;figure class="full"&gt;



&lt;img alt="" class="lazyload" src="../images/hiq/sofa_SDIM4612.jpg" /&gt;




&lt;/figure&gt;</description><author>Blogs on Michail Rybakov</author><pubDate>Fri, 27 Oct 2023 17:07:46 GMT</pubDate><guid isPermaLink="true">https://rybakov.com/blog/sofa/</guid></item><item><title>Suggested readings week 43/2023</title><link>https://www.stefanocosta.org/suggested-readings-week-43-2023/</link><description>&lt;hr /&gt;&lt;h3 id="creativity-and-leadership"&gt;&lt;a href="https://www.creativeldrs.com/?ref=stefanocosta.org" rel="nofollow noreferrer noopener"&gt;Creativity and Leadership&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Leadership is not Management, nor Coaching. Leadership as a practice, role and posture has been examined and dissected in a multitude of books and seminars since forever.&lt;/p&gt;&lt;p&gt;Here a totally new approach is suggested, that embraces Empathy and Creativity as traits of the effective Leader. The recently&lt;/p&gt;</description><author>Food for Thought (by Stefano Costa)</author><pubDate>Fri, 27 Oct 2023 15:42:50 GMT</pubDate><guid isPermaLink="true">https://www.stefanocosta.org/suggested-readings-week-43-2023/</guid></item><item><title>The contest of wills begins</title><link>https://mikewarot.blogspot.com/2023/10/the-contest-of-wills-begins.html</link><description>&lt;p&gt;&amp;nbsp;So, this morning when I click on my morning link to &lt;a href="https://twitter.com/PeterZeihan" target="_blank"&gt;Peter Zeihan&lt;/a&gt;, I've been greeted with this, instead of him talking from some new location.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/a/AVvXsEigrcpJAHtKUdi8OQ7n3m4rEgeuOmqjhFPpOqghC1cF39pWGdRYTrpbcpoGi8GRv6U4TZxpOIXTAxohJx07_llSSLpqeq3PeVEkW8gTH47OzEWRoHzkJl7Os0H3ZxVtZfiwA9PPogx-TspyZw7sC_9zgDuu5JBec0jWTN-RxYHIZvOxYrFiUWnv" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img alt="" height="240" src="https://blogger.googleusercontent.com/img/a/AVvXsEigrcpJAHtKUdi8OQ7n3m4rEgeuOmqjhFPpOqghC1cF39pWGdRYTrpbcpoGi8GRv6U4TZxpOIXTAxohJx07_llSSLpqeq3PeVEkW8gTH47OzEWRoHzkJl7Os0H3ZxVtZfiwA9PPogx-TspyZw7sC_9zgDuu5JBec0jWTN-RxYHIZvOxYrFiUWnv" width="246" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;We've entered the contest of wills. Google/YouTube thinks that they can force ads down my throat in service of their customers(the advertisers), and is willing to block my ad blocker in an attempt to force my compliance.&lt;br /&gt;&lt;br /&gt;I'm sure there will eventually be a different platform to watch Peter's videos on... and the wait begins. We programmers/technicians have a superpower, we're willing to put up with a ton of grief to bend things to our will, it's how we route around the normal rules and get shit done.&lt;br /&gt;&lt;br /&gt;I curate my sources, and Peter is one of them, I'm not willing to pollute that stream of information with the Ads that Google wants to force feed me. It'll be interesting to see how this all works out.&lt;p&gt;&lt;/p&gt;</description><author>--Mike--</author><pubDate>Fri, 27 Oct 2023 14:53:03 GMT</pubDate><guid isPermaLink="true">https://mikewarot.blogspot.com/2023/10/the-contest-of-wills-begins.html</guid></item><item><title>FatturaElettronica for .NET v3.4.11</title><link>https://nicolaiarocci.com/fatturaelettronica-for-.net-v3.4.11/</link><description>&lt;p&gt;&lt;a href="https://fatturaelettronicaopensource.org/docs"&gt;Fattura Elettronica&lt;/a&gt; for .NET v3.4.11 was released on NuGet today. The Fattura Elettronica project allows for the
fixes a missing validation point. See the &lt;a href="https://fatturaelettronicaopensource.org/docs/changelog.html#v-3411"&gt;changelog&lt;/a&gt; for details (Italian). validation and de/serialization of
electronic invoices following the Italian Revenue Agency standards.&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Fri, 27 Oct 2023 09:05:25 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/fatturaelettronica-for-.net-v3.4.11/</guid></item><item><title>RetroFit</title><link>https://lovebloodrhetoric.com/2023/10/26/retrofit/</link><description>“So, how does this work, exactly?” “First step is intake. That’s what we’re doing now. We’ll determine your goals and map out a strategy for how to achieve them. You’ll train in our bubble—the entire facility is temporally isolated—and we’ll reinsert you into the timestream when you’re ready. Guaranteed return within ten minutes of pickup. [&amp;#8230;]</description><author>Love, Blood &amp;amp; Rhetoric</author><pubDate>Fri, 27 Oct 2023 07:13:31 GMT</pubDate><guid isPermaLink="true">https://lovebloodrhetoric.com/2023/10/26/retrofit/</guid></item><item><title>Prototyping a PDF Chatbot from Scratch</title><link>http://www.gregreda.com/2023/10/26/prototyping-a-pdf-chatbot-from-scratch/</link><description>&lt;p&gt;As part of my work on &lt;a href="https://github.com/refstudio/refstudio"&gt;refstudio&lt;/a&gt;, I spent some time prototyping a chatbot that could answer questions about a corpus of PDFs. Tools like &lt;a href="https://github.com/langchain-ai/langchain"&gt;LangChain&lt;/a&gt;, &lt;a href="https://github.com/run-llama/llama_index"&gt;LlamaIndex&lt;/a&gt;, &lt;a href="https://github.com/deepset-ai/haystack"&gt;Haystack&lt;/a&gt;, and others all have built-in abstractions to simplify this task, but I find that building a simplified version from scratch helps me …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2023/10/26/prototyping-a-pdf-chatbot-from-scratch/</guid></item><item><title>Django Command for FIT files</title><link>http://www.gregreda.com/2023/05/21/django-command-for-fit-files/</link><description>&lt;p&gt;FIT - &lt;a href="https://developer.garmin.com/fit/protocol/"&gt;Flexible and Interoperable Transfer&lt;/a&gt; - is a protocol designed for storing and sharing data from fitness and health devices.&lt;/p&gt;
&lt;p&gt;Since getting a &lt;a href="https://coros.com/"&gt;Coros&lt;/a&gt; running watch in July 2022, I've been exporting the FIT file data to Dropbox after every run.&lt;/p&gt;
&lt;p&gt;Having all of this data laying around seemed like a …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2023/05/21/django-command-for-fit-files/</guid></item><item><title>Notes on using PyInstaller, poetry, and pyenv</title><link>http://www.gregreda.com/2023/05/18/notes-on-using-pyinstaller-poetry-and-pyenv/</link><description>&lt;p&gt;In my all my years of working in Python, I don't think I've ever had to create a standalone executable. But, it finally happened.&lt;/p&gt;
&lt;p&gt;It was a pretty seamless experience, but I did hit a minor hiccup, so I wanted to capture some notes for my future self (and others …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2023/05/18/notes-on-using-pyinstaller-poetry-and-pyenv/</guid></item><item><title>Assorted bits: 2022-12-09</title><link>http://www.gregreda.com/2022/12/09/assorted-bits-2022-12-09/</link><description>&lt;p&gt;I’ve wanted to get in the habit of writing more, so I’m taking some inspiration from old school blogs and sharing some things I've recently enjoyed.&lt;/p&gt;
&lt;p&gt;Enjoy your weekend!&lt;/p&gt;
&lt;h3&gt;[Music] Spectrum by Max Cooper&lt;/h3&gt;


&lt;p&gt;I'm very into synthesizer centric music. I'm also into music that creates a sonic …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2022/12/09/assorted-bits-2022-12-09/</guid></item><item><title>This One's For Me</title><link>http://www.gregreda.com/2022/11/30/this-ones-for-me/</link><description>&lt;p&gt;I had a heart attack earlier this year. A &lt;a href="https://en.wikipedia.org/wiki/Left_anterior_descending_artery#Widow_maker"&gt;"widow maker" heart attack&lt;/a&gt;. I fell into cardiac arrest and was clinically dead for a couple of minutes. I'm incredibly fortunate two nurses happened to be nearby. They defibrillated me and administered CPR until the ambulance arrived and took me to …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2022/11/30/this-ones-for-me/</guid></item><item><title>Reviving this space</title><link>http://www.gregreda.com/2022/11/18/reviving-this-space/</link><description>&lt;p&gt;Despite regularly wanting to write more, I haven’t written anything in 18 months. I have countless drafts that I can’t seem to finish, or at least get into a place I feel comfortable publishing. I've mentally blocked myself from doing so.&lt;/p&gt;
&lt;p&gt;I can think of plenty of reasons …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2022/11/18/reviving-this-space/</guid></item><item><title>Mocking an imported module-level function in Python</title><link>http://www.gregreda.com/2021/06/28/mocking-imported-module-function-python/</link><description>&lt;p&gt;The other day I spent far too much time trying to figure out how to mock a module-level function that was being used inside of my class's method in Python. My googling didn't lead to obvious answers, so I figured it'd be good to document here for future reference.&lt;/p&gt;
&lt;p&gt;Imagine …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2021/06/28/mocking-imported-module-function-python/</guid></item><item><title>Using Go and Twilio to monitor my email</title><link>http://www.gregreda.com/2020/12/11/using-go-and-twilio-to-monitor-my-email/</link><description>&lt;p&gt;Sometimes I'm expecting an email and want to be notified shortly after receiving it. But I also don't want to stare at my inbox, something I don't particularly enjoy checking in the first place.&lt;/p&gt;
&lt;p&gt;To illustrate an example, imagine you're browsing a niche site with limited edition goods, some of …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2020/12/11/using-go-and-twilio-to-monitor-my-email/</guid></item><item><title>Deploying static sites with Github Actions</title><link>http://www.gregreda.com/2020/12/09/deploying-static-sites-with-github-actions/</link><description>&lt;p&gt;A while back I &lt;a href="http://gregreda.com/2015/03/26/static-site-deployments/"&gt;wrote&lt;/a&gt; about deploying my site using Github and Travis CI. But recently it seems &lt;a href="https://news.ycombinator.com/item?id=25338983"&gt;Travis CI stopped being free for open source projects&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you're using a static site generator for your site and hosting it on it on S3, you can use &lt;a href="https://docs.github.com/en/free-pro-team@latest/actions"&gt;Github Actions&lt;/a&gt; to …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2020/12/09/deploying-static-sites-with-github-actions/</guid></item><item><title>newbird: a theme for pelican</title><link>http://www.gregreda.com/2020/11/25/newbird-pelican-theme/</link><description>&lt;p&gt;In 2014, I &lt;a href="https://github.com/gjreda/void"&gt;wrote a custom theme&lt;/a&gt; for &lt;a href="https://blog.getpelican.com/"&gt;Pelican&lt;/a&gt;, the static site generator I use for this site.&lt;/p&gt;
&lt;p&gt;At the time, there were few themes available and I wanted something that was fairly simple in its design, but also that I understood well enough to tweak as necessary. I opted …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2020/11/25/newbird-pelican-theme/</guid></item><item><title>Scraping pages behind login forms</title><link>http://www.gregreda.com/2020/11/17/scraping-pages-behind-login-forms/</link><description>&lt;p&gt;&lt;em&gt;This is part of a series of posts I have written about web scraping with Python.&lt;/em&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="http://www.gregreda.com/2013/03/03/web-scraping-101-with-python/"&gt;Web Scraping 101 with Python&lt;/a&gt;, which covers the basics of using Python for web scraping.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.gregreda.com/2015/02/15/web-scraping-finding-the-api/"&gt;Web Scraping 201: Finding the API&lt;/a&gt;, which covers when sites load data client-side with Javascript.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.gregreda.com/2016/10/16/asynchronous-scraping-with-python/"&gt;Asynchronous Scraping with Python …&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2020/11/17/scraping-pages-behind-login-forms/</guid></item><item><title>Feature Engineering with Time Gaps</title><link>http://www.gregreda.com/2020/02/16/feature-engineering-with-time-gaps/</link><description>&lt;p&gt;I tend to forget how to write certain blocks of code when I haven't written them in a while. Here's a common machine learning preprocessing task that falls into that category.&lt;/p&gt;
&lt;p&gt;Imagine you have some event logs that capture an entity ID (user, store, ad, etc), timestamp, an event name …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2020/02/16/feature-engineering-with-time-gaps/</guid></item><item><title>Lenny Dykstra, His Strike Zone, &amp;amp; Bayesian Stats</title><link>http://www.gregreda.com/2018/07/08/dykstra-strike-zone-bayesian-stats/</link><description>&lt;p&gt;In 2015, former Major Leaguer Lenny Dykstra went on Colin Cowherd’s radio show and claimed that he used to hire private investigators to find dirt on umpires. The intention of doing so was to turn that dirt into a more favorable strike zone for himself. You can find the …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2018/07/08/dykstra-strike-zone-bayesian-stats/</guid></item><item><title>Hiring Data Scientists</title><link>http://www.gregreda.com/2018/02/04/hiring-data-scientists/</link><description>&lt;p&gt;Chicago's a big city that feels small -- everyone seems only a degree or two
away from one another. This feels especially true within Chicago's tech and data
science communities.&lt;/p&gt;
&lt;p&gt;As a result, I occasionally get asked about hiring data scientists.
Specifically, how do you vet, hire, and evaluate a data …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2018/02/04/hiring-data-scientists/</guid></item><item><title>My Experience as a Freelance Data Scientist</title><link>http://www.gregreda.com/2017/01/07/freelance-data-science-experience/</link><description>&lt;p&gt;Every so often, data scientists who are thinking about going off on their own will email me with questions about my year of freelancing (2015). In my most recent response, I was a little more detailed than usual, so I figured it'd make sense as a blog post too.&lt;/p&gt;
&lt;p&gt;If …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2017/01/07/freelance-data-science-experience/</guid></item><item><title>[Talk] Data-Informed vs Data-Driven</title><link>http://www.gregreda.com/2016/11/20/data-informed-pydata-video/</link><description>&lt;p&gt;A while back (July 2015), I was fortunate to speak at PyData in Seattle about the pitfalls of &lt;em&gt;overreliance&lt;/em&gt; on data. You can find the slides &lt;a href="https://github.com/gjreda/pydata2015sea/blob/4a846a7d069601cc5f886e53863a17d7fd68f2a8/data-informed-vs-data-driven-with-notes.pdf"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;My talk centered around my belief that the term "data-driven," when taken at its face value, should not be something we strive for …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2016/11/20/data-informed-pydata-video/</guid></item><item><title>Asynchronous Scraping with Python</title><link>http://www.gregreda.com/2016/10/16/asynchronous-scraping-with-python/</link><description>&lt;p&gt;&lt;em&gt;This is part of a series of posts I have written about web scraping with Python.&lt;/em&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="http://www.gregreda.com/2013/03/03/web-scraping-101-with-python/"&gt;Web Scraping 101 with Python&lt;/a&gt;, which covers the basics of using Python for web scraping.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.gregreda.com/2015/02/15/web-scraping-finding-the-api/"&gt;Web Scraping 201: Finding the API&lt;/a&gt;, which covers when sites load data client-side with Javascript.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.gregreda.com/2016/10/16/asynchronous-scraping-with-python/"&gt;Asynchronous Scraping with Python …&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2016/10/16/asynchronous-scraping-with-python/</guid></item><item><title>Visualizing the 2015 NL Cy Young Race</title><link>http://www.gregreda.com/2015/11/19/nl-cyyoung-viz-2015/</link><description>&lt;p&gt;This year's National League Cy Young race was pretty much a toss-up, with each of
&lt;a href="http://www.baseball-reference.com/players/a/arrieja01.shtml"&gt;Jake Arrieta&lt;/a&gt;, &lt;a href="http://www.baseball-reference.com/players/g/greinza01.shtml"&gt;Zack Greinke&lt;/a&gt;, and &lt;a href="http://www.baseball-reference.com/players/k/kershcl01.shtml"&gt;Clayton Kershaw&lt;/a&gt; putting up numbers
we haven't seen in a decade or more.&lt;/p&gt;
&lt;p&gt;By now we know that Arrieta wins the award, but being the Cubs homer I am, I …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2015/11/19/nl-cyyoung-viz-2015/</guid></item><item><title>Cohort Analysis with Python</title><link>http://www.gregreda.com/2015/08/23/cohort-analysis-with-python/</link><description>&lt;p&gt;Despite having done it countless times, I regularly forget how to build a &lt;a href="https://en.wikipedia.org/wiki/Cohort_analysis"&gt;cohort analysis&lt;/a&gt; with Python and &lt;a href="http://pandas.pydata.org/"&gt;pandas&lt;/a&gt;. I’ve decided it’s a good idea to finally write it out - step by step - so I can refer back to this post later on. Hopefully others find it useful …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2015/08/23/cohort-analysis-with-python/</guid></item><item><title>Nonsensical beer reviews via Markov chains</title><link>http://www.gregreda.com/2015/03/30/beer-review-markov-chains/</link><description>&lt;p&gt;I’ve had a bunch of beer reviews and ratings data sitting on my hard drive for about year. For a beer nerd like me, that’s a pretty cool dataset, yet I’ve let it collect digital dust.&lt;/p&gt;
&lt;p&gt;Fast forward to last week, where somehow I wound up in …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2015/03/30/beer-review-markov-chains/</guid></item><item><title>Using Travis &amp;amp; GitHub to deploy static sites</title><link>http://www.gregreda.com/2015/03/26/static-site-deployments/</link><description>&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; As of December 2020, Travis CI has stopped being free for open source projects. If you've been using Travis to deploy your static site, I recommend migrating to Github Actions. I've written a post about how to do so &lt;a href="/2020/12/09/deploying-static-sites-with-github-actions/"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;hr /&gt;

&lt;p&gt;I’m an unabashed supporter of “Keep It Simple …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2015/03/26/static-site-deployments/</guid></item><item><title>Web Scraping 201: finding the API</title><link>http://www.gregreda.com/2015/02/15/web-scraping-finding-the-api/</link><description>&lt;p&gt;&lt;em&gt;This is part of a series of posts I have written about web scraping with Python.&lt;/em&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="http://www.gregreda.com/2013/03/03/web-scraping-101-with-python/"&gt;Web Scraping 101 with Python&lt;/a&gt;, which covers the basics of using Python for web scraping.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.gregreda.com/2015/02/15/web-scraping-finding-the-api/"&gt;Web Scraping 201: Finding the API&lt;/a&gt;, which covers when sites load data client-side with Javascript.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.gregreda.com/2016/10/16/asynchronous-scraping-with-python/"&gt;Asynchronous Scraping with Python …&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2015/02/15/web-scraping-finding-the-api/</guid></item><item><title>[Talk] Translating SQL to pandas</title><link>http://www.gregreda.com/2014/12/22/translating-sql-to-pandas-video/</link><description>&lt;p&gt;A few weeks ago, I gave a &lt;a href="http://pandas.pydata.org/"&gt;pandas&lt;/a&gt; tutorial at &lt;a href="http://pydata.org/nyc2014/"&gt;PyData NYC&lt;/a&gt; titled "Translating SQL to pandas. And back." I don't remember why I put the "And back" in there - if you can translate things one way, you can translate them the other way, too.&lt;/p&gt;
&lt;p&gt;Anyway, here's the abstract:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;SQL …&lt;/p&gt;&lt;/blockquote&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2014/12/22/translating-sql-to-pandas-video/</guid></item><item><title>Scraping Craigslist for sold out concert tickets</title><link>http://www.gregreda.com/2014/07/27/scraping-craigslist-for-tickets/</link><description>&lt;p&gt;Recently, I've been listening to a lot of lo-fi rock band, &lt;a href="http://en.wikipedia.org/wiki/Cloud_Nothings"&gt;Cloud Nothings&lt;/a&gt;. Their album, &lt;a href="http://www.amazon.com/gp/product/B00HZJH97Q/ref=as_li_tl?ie=UTF8&amp;amp;camp=1789&amp;amp;creative=390957&amp;amp;creativeASIN=B00HZJH97Q&amp;amp;linkCode=as2&amp;amp;tag=gjreda-20&amp;amp;linkId=H7HYP35ZYKFAKH7H"&gt;Here &amp;amp; Nowhere Else&lt;/a&gt;, has been &lt;a href="http://www.metacritic.com/music/here-and-nowhere-else/cloud-nothings"&gt;critically lauded&lt;/a&gt;, including &lt;a href="http://pitchfork.com/reviews/albums/19075-cloud-nothings-here-and-nowhere-else/"&gt;garnering "Best New Music" from Pitchfork&lt;/a&gt;. As a result, when they came to Chicago's tiny Lincoln Hall in May, tickets sold out in a hurry - well before …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2014/07/27/scraping-craigslist-for-tickets/</guid></item><item><title>Principles of good data analysis</title><link>http://www.gregreda.com/2014/03/23/principles-of-good-data-analysis/</link><description>&lt;p&gt;Data analysis is hard.&lt;/p&gt;
&lt;p&gt;What makes it hard is the intuitive aspect of it - knowing the direction you want to take based on the limited information you have at the moment. Additionally, it's communicating the results and showing &lt;em&gt;why&lt;/em&gt; your analysis is right that makes this all the more difficult …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2014/03/23/principles-of-good-data-analysis/</guid></item><item><title>Finding the midpoint of film releases</title><link>http://www.gregreda.com/2014/01/23/film-releases-midpoint/</link><description>&lt;blockquote&gt;
&lt;p&gt;"We're talking …&lt;/p&gt;&lt;/blockquote&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2014/01/23/film-releases-midpoint/</guid></item><item><title>3-pointers after offensive rebounds</title><link>http://www.gregreda.com/2013/12/26/three-pointers-after-offensive-rebounds/</link><description>&lt;p&gt;I love college basketball. A lot. My beloved Marquette Golden Eagles are probably the only sports team that can really put me in agony.&lt;/p&gt;
&lt;p&gt;Last season, I was watching a &lt;a href="https://www.espn.com/mens-college-basketball/recap?gameId=330730269"&gt;nail-biter against Notre Dame&lt;/a&gt;. With 3:36 left, Notre Dame's Jack Cooley grabbed an offensive board and promptly passed out …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2013/12/26/three-pointers-after-offensive-rebounds/</guid></item><item><title>Using pandas on the MovieLens dataset</title><link>http://www.gregreda.com/2013/10/26/using-pandas-on-the-movielens-dataset/</link><description>&lt;p&gt;&lt;em&gt;UPDATE: If you're interested in learning pandas from a SQL perspective and would prefer to watch a video, you can find video of my 2014 PyData NYC talk &lt;a href="http://reda.io/sql2pandas"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part three of a three part introduction to &lt;a href="http://pandas.pydata.org"&gt;pandas&lt;/a&gt;, a Python library for data analysis. The tutorial is primarily …&lt;/em&gt;&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2013/10/26/using-pandas-on-the-movielens-dataset/</guid></item><item><title>Working with DataFrames</title><link>http://www.gregreda.com/2013/10/26/working-with-pandas-dataframes/</link><description>&lt;p&gt;&lt;em&gt;UPDATE: If you're interested in learning pandas from a SQL perspective and would prefer to watch a video, you can find video of my 2014 PyData NYC talk &lt;a href="http://reda.io/sql2pandas"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part two of a three part introduction to &lt;a href="http://pandas.pydata.org"&gt;pandas&lt;/a&gt;, a Python library for data analysis. The tutorial is primarily …&lt;/em&gt;&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2013/10/26/working-with-pandas-dataframes/</guid></item><item><title>Intro to pandas data structures</title><link>http://www.gregreda.com/2013/10/26/intro-to-pandas-data-structures/</link><description>&lt;p&gt;&lt;em&gt;UPDATE: If you're interested in learning pandas from a SQL perspective and would prefer to watch a video, you can find video of my 2014 PyData NYC talk &lt;a href="http://reda.io/sql2pandas"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="/2013/01/23/translating-sql-to-pandas-part1/"&gt;A while back I claimed&lt;/a&gt; I was going to write a couple of posts on translating &lt;a href="http://pandas.pydata.org"&gt;pandas&lt;/a&gt; to SQL. I never …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2013/10/26/intro-to-pandas-data-structures/</guid></item><item><title>New theme for Pelican</title><link>http://www.gregreda.com/2013/10/24/new-theme-for-pelican/</link><description>&lt;p&gt;I spent some time last weekend making minor changes to this site. Specifically:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;New typography - headers are &lt;a href="http://www.google.com/fonts/specimen/Droid+Serif"&gt;Droid Serif&lt;/a&gt;, while everything else is &lt;a href="http://www.google.com/fonts/specimen/Droid+Sans"&gt;Droid Sans&lt;/a&gt;. Fonts are also a bit bigger (I think it's easier to read).&lt;/li&gt;
&lt;li&gt;Added &lt;a href="http://jakevdp.github.io/"&gt;Jake Vanderplas'&lt;/a&gt; &lt;a href="https://github.com/getpelican/pelican-plugins/tree/master/liquid_tags"&gt;liquid tags plugin&lt;/a&gt; for Pelican, which allows for easy embedding …&lt;/li&gt;&lt;/ol&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2013/10/24/new-theme-for-pelican/</guid></item><item><title>Useful Unix commands for data science</title><link>http://www.gregreda.com/2013/07/15/unix-commands-for-data-science/</link><description>&lt;p&gt;Imagine you have a 4.2GB CSV file.  It has over 12 million records and 50 columns.  All you need from this file is the sum of all values in one particular column.&lt;/p&gt;
&lt;p&gt;How would you do it?&lt;/p&gt;
&lt;p&gt;Writing a script in &lt;a href="http://www.python.org/"&gt;python&lt;/a&gt;/&lt;a href="http://www.ruby-lang.org/"&gt;ruby&lt;/a&gt;/&lt;a href="http://www.perl.org/"&gt;perl&lt;/a&gt;/whatever would probably take a …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2013/07/15/unix-commands-for-data-science/</guid></item><item><title>How random is JavaScript's Math.random()?</title><link>http://www.gregreda.com/2013/06/30/testing-javascripts-random-function/</link><description>&lt;p&gt;A few weeks back, I was talking with my friend &lt;a href="http://mollybierman.tumblr.com"&gt;Molly&lt;/a&gt; about personal domains and realized that her nickname, Bierface, was available.  The exchange basically went like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Me: I should buy bierface.com and just put up a ridiculous picture of you.&lt;/p&gt;
&lt;p&gt;Molly: You would have to do a …&lt;/p&gt;&lt;/blockquote&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2013/06/30/testing-javascripts-random-function/</guid></item><item><title>Join vs Exists vs In (SQL)</title><link>http://www.gregreda.com/2013/06/03/join-vs-exists-vs-in/</link><description>&lt;p&gt;Last weekend, I came across &lt;a href="http://en.wikipedia.org/wiki/Jeff_Atwood"&gt;Jeff Atwood&lt;/a&gt;'s excellent &lt;a href="http://www.codinghorror.com/blog/2007/10/a-visual-explanation-of-sql-joins.html"&gt;visual explanation of SQL joins&lt;/a&gt; on Hacker News.&lt;/p&gt;
&lt;p&gt;It reminded me of teaching SQL to the incoming batch of &lt;a href="http://www.pwc.com/us/en/forensic-services/technology-solutions.jhtml"&gt;PwC FTS&lt;/a&gt; associates a few years ago.  Not many of them had prior programming experience, much less SQL exposure, so it was …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2013/06/03/join-vs-exists-vs-in/</guid></item><item><title>More web scraping with Python (and a map)</title><link>http://www.gregreda.com/2013/04/29/more-web-scraping-with-python/</link><description>&lt;p&gt;&lt;em&gt;This is a follow-up to my &lt;a href="/2013/03/03/web-scraping-101-with-python/" title="Web Scraping 101 with Python"&gt;previous post&lt;/a&gt; about web scraping with Python&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Previously, I wrote a basic intro to scraping data off of websites.  Since I wanted to keep the intro fairly simple, I didn't cover storing the data.  In this post, I'll cover the basics of writing the …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2013/04/29/more-web-scraping-with-python/</guid></item><item><title>Write online about what you love</title><link>http://www.gregreda.com/2013/03/16/why-you-should-write-online/</link><description>&lt;p&gt;The other week, I wrote a &lt;a href="http://www.gregreda.com/2013/03/03/web-scraping-101-with-python/"&gt;very basic intro to web scraping with Python&lt;/a&gt;.  Some friends knew that I had experience scraping data and they wanted to learn, so I figured it would be a great opportunity to write something publicly and test how well I could explain it.&lt;/p&gt;
&lt;p&gt;I'll …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2013/03/16/why-you-should-write-online/</guid></item><item><title>Web Scraping 101 with Python</title><link>http://www.gregreda.com/2013/03/03/web-scraping-101-with-python/</link><description>&lt;p&gt;&lt;em&gt;This is part of a series of posts I have written about web scraping with Python.&lt;/em&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="http://www.gregreda.com/2013/03/03/web-scraping-101-with-python/"&gt;Web Scraping 101 with Python&lt;/a&gt;, which covers the basics of using Python for web scraping.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.gregreda.com/2015/02/15/web-scraping-finding-the-api/"&gt;Web Scraping 201: Finding the API&lt;/a&gt;, which covers when sites load data client-side with Javascript.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.gregreda.com/2016/10/16/asynchronous-scraping-with-python/"&gt;Asynchronous Scraping with Python …&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2013/03/03/web-scraping-101-with-python/</guid></item><item><title>Translating SQL to Pandas, Part 1</title><link>http://www.gregreda.com/2013/01/23/translating-sql-to-pandas-part1/</link><description>&lt;p&gt;&lt;em&gt;I wrote a three part pandas tutorial for SQL users that you can find &lt;a href="http://www.gregreda.com/2013/10/26/intro-to-pandas-data-structures/"&gt;here&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;UPDATE: If you're interested in learning pandas from a SQL perspective and would prefer to watch a video, you can find video of my 2014 PyData NYC talk &lt;a href="http://reda.io/sql2pandas"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For some reason, I've always found …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2013/01/23/translating-sql-to-pandas-part1/</guid></item><item><title>Hello World</title><link>http://www.gregreda.com/2013/01/22/hello-world/</link><description>&lt;p&gt;So I finally got around to putting something of my own up.&lt;/p&gt;
&lt;p&gt;My intentions are mainly to use this space as a way to document mini projects that I'm working on, so plan on it being pretty programming, data, visualization, and statistics heavy - I get bored if I'm not learning …&lt;/p&gt;</description><author>Greg Reda</author><pubDate>Fri, 27 Oct 2023 04:23:07 GMT</pubDate><guid isPermaLink="true">http://www.gregreda.com/2013/01/22/hello-world/</guid></item><item><title>Enhancing IPFS Performance in Kubernetes Environments</title><link>https://thoughts.greyh.at/posts/kubernetes-ipfs/</link><description>For over half a decade, I have been deploying IPFS on my Kubernetes cluster, gaining valuable insights into its operation within such an environment. Even though it&amp;rsquo;s just a modest singleton deployment, the experience has led to a deeper understanding of various nuances and potential pitfalls. This article aims to share the learned lessons and offer configuration guidelines to optimize IPFS performance in a Kubernetes setup.</description><author>Terminal Thoughts</author><pubDate>Fri, 27 Oct 2023 03:44:55 GMT</pubDate><guid isPermaLink="true">https://thoughts.greyh.at/posts/kubernetes-ipfs/</guid></item><item><title>State of my projects for October 2023</title><link>https://benovermyer.com/blog/2023/10/state-of-my-projects-for-october-2023/</link><description>&lt;p&gt;I have a lot of irons in the fire. Here's where everything I'm working on this year is at.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ingenium Second Edition:&lt;/strong&gt; The playtest book is out. The cover art is done, and I've started
collecting more interior art. Sarah will be editing it in the not-too-distant future. I don't
think this will be published until next year.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Aether Second Edition:&lt;/strong&gt; The playtest book for this is also out. Editing is in progress. It needs
both cover art and interior art. I might hire a friend of mine who worked on the Nostalgix TCG
to do graphic design and layout for it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Aethermancy:&lt;/strong&gt; Kevin is still working on the manuscript for this one. Of all the Silver Gryphon
Games projects, this is the one that I wanted to get out the door in 2023, but now I have doubts
about that happening. At least all the artwork is done, and the editing is mostly done, except
for the chapters Kevin is still writing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Vox Draconis: Kingdoms of Stone and Fire:&lt;/strong&gt; This guy's been on hold almost the entire year.
I've been focusing my RPG-writing efforts on Silver Gryphon projects.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mysteries of a Broken World:&lt;/strong&gt; Like Vox Draconis, this game has taken a back seat to SGG products.
I won't be working on it this year.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AmigaSource:&lt;/strong&gt; This one's new. The current maintainer reached out to me asking for assistance in
improving the site. I'm in the process of rewriting the site mostly from scratch, but keeping
the NCSA Mosaic-friendly HTML.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tayledras Fan Site:&lt;/strong&gt; This is another new project this year. I originally planned to just
resurrect my old Tayledras Fan Club website, but after nixing all of the "club" content,
it was pretty bare bones. So I'm going to make it a knowledge base for the fictional
Tayledras culture as presented in the Valdemar novels by Mercedes Lackey. However, I won't
begin to write the content for the site until I've finished reading every novel that the
Tayledras appear in. At time of writing, I'm working on Arrows of the Queen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Iron Arachne:&lt;/strong&gt; This year I started breaking pieces of the site out into their own NPM
packages. Some of them are seeing use by other people now. I'm going to expand on this concept
as I have time. Someone reached out to me asking if I could turn the dungeon generation and
star system generation modules into NPM packages.&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Fri, 27 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2023/10/state-of-my-projects-for-october-2023/</guid></item><item><title>The White Ladies</title><link>https://godsip.club/articles/white-ladies/</link><description>&lt;p&gt;During &lt;a href="https://scaglio.bearblog.dev/go-visit-dordogne/" rel="noopener" target="_blank"&gt;my last vacation in France&lt;/a&gt;, we came across one of the dozens of medieval castles open to visitors: the &lt;a href="https://en.wikipedia.org/wiki/Ch%C3%A2teau_de_Puymartin" rel="noopener" target="_blank"&gt;Château de Puymartin&lt;/a&gt;.
The structure itself was amazing to visit, but with great surprise I found a couple of floors of the ancient tower full of mythological paintings and related panels.
Needless to say, we spent at least thirty minutes over there.
Then, walking up to the next level, my mind was blown: there were two whole rooms dedicated to the local folklore! 🤯&lt;/p&gt;
&lt;figure&gt;&lt;img alt="To quote George and his coffee… &amp;ldquo;What else?&amp;rdquo;" src="https://godsip.club/img/mcmahon-castle.jpg" /&gt;&lt;figcaption&gt;
      &lt;p&gt;To quote George and his coffee… &amp;ldquo;What else?&amp;rdquo;&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Among many interesting insights, there were some about the mysterious figure of a woman, dressed in white, that haunted the castle.
On the panels, she was referred as &lt;em&gt;White Lady&lt;/em&gt;.
I immediately thought about a &amp;ldquo;local ghost&amp;rdquo;, but reading on, I discovered that this figure is much more spread than I thought.
This article will focus on this being and some other ghastly findings proper to the spooky season. 👻&lt;/p&gt;
&lt;h1 id="the-washerwomen"&gt;The Washerwomen &lt;a class="anchor" href="#the-washerwomen"&gt;#&lt;/a&gt;&lt;/h1&gt;&lt;p&gt;Reported sightings of spectral figures dressed in white come from all over the world, and understandably inspired a gazillion different stories.
The White Ladies were first &amp;ldquo;reported&amp;rdquo; in Europe in the Middle Ages in the form of fairies or supernatural creatures.
They were often linked with other eerie creatures as goblin and forest spirits, and they weren&amp;rsquo;t always seen as malevolent.&lt;/p&gt;
&lt;p&gt;They were, indeed, linked to to &lt;em&gt;water&lt;/em&gt;: White Ladies always appeared at night, by a river, alone or in a group of &lt;strong&gt;washerwomen&lt;/strong&gt;.
Because of that, they have been linked with &lt;a href="https://en.wikipedia.org/wiki/Melusine" rel="noopener" target="_blank"&gt;Melusines&lt;/a&gt; and, later, with &lt;a href="https://en.wikipedia.org/wiki/Undine" rel="noopener" target="_blank"&gt;Undines&lt;/a&gt;, creatures with their half lower body of a fish or serpent.
These particular two were spread in France and Germany, but beings with these characteristics have always filled mankind&amp;rsquo;s imagination.
Each region of the world has its own legends and names: &lt;a href="https://en.wikipedia.org/wiki/Selkie" rel="noopener" target="_blank"&gt;Selkies&lt;/a&gt; (Shetlands), &lt;a href="https://en.wikipedia.org/wiki/Mermaid" rel="noopener" target="_blank"&gt;Merrows and Mermaids&lt;/a&gt; (Ireland), &lt;a href="https://en.wikipedia.org/wiki/Morgen_%28mythological_creature%29" rel="noopener" target="_blank"&gt;Morgen&lt;/a&gt; (Wales), &lt;a href="https://en.wikipedia.org/wiki/Siren_%28mythology%29" rel="noopener" target="_blank"&gt;Sirens&lt;/a&gt; (Greece), and &lt;a href="https://en.wikipedia.org/wiki/Rusalka" rel="noopener" target="_blank"&gt;Rusalki&lt;/a&gt; (Slavic countries) have always depicted creatures deeply linked with water.&lt;/p&gt;
&lt;p&gt;Long before Christianity, water was venerated and linked to the &lt;em&gt;feminine figure&lt;/em&gt;.
Then, the Church condemned any pagan apparition as demonic, and introduced some confusion between fairies and witches, virgins and saints,… washerwomen and White Ladies.
Nowadays, however, there are still plenty of places associated with cults of nature, water and healing.&lt;/p&gt;
&lt;figure&gt;&lt;img alt="&amp;ldquo;Rusalki&amp;rdquo;, or &amp;ldquo;The Mermaids&amp;rdquo;, by Iwan Kramoskoi, 1871. (Wikimedia Commons)" src="https://godsip.club/img/rusalki.jpg" /&gt;&lt;figcaption&gt;
      &lt;p&gt;&amp;ldquo;Rusalki&amp;rdquo;, or &amp;ldquo;The Mermaids&amp;rdquo;, by Iwan Kramoskoi, 1871. (Wikimedia Commons)&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Focusing on the White Ladies, they can be found in many tales from Celtic culture and Southern Europe. They were seen as ghosts, or &lt;em&gt;messengers&lt;/em&gt;, of young women dead in tragic circumstances.
They came from noble families, so the spirits were mostly found around castles.
After the French Revolution in 1789, mistrust rose against nobility, and castles became sources of legends with curses, ghosts,… and &lt;em&gt;wolves&lt;/em&gt; — we&amp;rsquo;ll see that in a minute.
The Ladies&amp;rsquo; representation, since they were linked to the places they haunted, then changed consequently: they became ghosts of women who had buried their husbands in dirty shrouds, or mothers who had killed their children… and were depicted as silent, or shrieking funeral laments, to announce a future death — as &lt;a href="https://en.wikipedia.org/wiki/Banshee" rel="noopener" target="_blank"&gt;Banshees&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id="dance-owl-night"&gt;Dance Owl Night &lt;a class="anchor" href="#dance-owl-night"&gt;#&lt;/a&gt;&lt;/h1&gt;&lt;p&gt;Many lonely or nocturnal animals, because of their –at the time– inexplicable behavior, were deeply linked with supernatural appearances.
Wolves were extremely dangerous for both man&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; and grazing animals, so they quickly were bounded with evil forces, or witches.
These animals were so feared that only gods could control them, as &lt;em&gt;&lt;a href="https://en.wikipedia.org/wiki/Dazhbog" rel="noopener" target="_blank"&gt;Dazbog&lt;/a&gt;&lt;/em&gt;, who was called &amp;ldquo;Shepard of Wolves&amp;rdquo;.
He was pictured as a lame old man without an eye — like &lt;em&gt;Odin&lt;/em&gt;, being without an eye meant to be able to see into the Spirit World.&lt;/p&gt;
&lt;p&gt;The cat, especially if black, is the most notorious example of animal associated to witches.
In &lt;em&gt;The Golden Bough&lt;/em&gt;, Frazer tells &lt;a href="https://en.wikisource.org/wiki/The_Golden_Bough/The_Corn-Spirit_as_an_Animal" rel="noopener" target="_blank"&gt;some examples&lt;/a&gt; of how that poor animal was treated when corn was harvested.
They were also believed to have some magical powers: if a farmer hurt himself, he had the cut licked by a cat.&lt;/p&gt;
&lt;figure&gt;&lt;img alt="This card from 1907 sums up Halloween quite well. (Wikimedia Commons)" src="https://godsip.club/img/halloween-card-witch.jpg" /&gt;&lt;figcaption&gt;
      &lt;p&gt;This card from 1907 sums up Halloween quite well. (Wikimedia Commons)&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;However, the animal that was most linked with White Ladies was the &lt;strong&gt;Barn Owl&lt;/strong&gt;.
Its plaintive, almost human, cry was associated with the terrifying howl of the wolf on the edge of the dark forest.
Also, its nocturnal life and ghostly pallor surely didn&amp;rsquo;t help to get fondness by folks.
The barn owl is often seen as an ill-omen, &lt;em&gt;a White Lady&lt;/em&gt;, an intimate of evil spirits.
In Spain, it was believed that those birds drank oil from lanterns in churches, leaving priests in total darkness — this explained why they hated daylight.
So, hunters killed the owls and hanged them just outside the buildings, to avoid them to enter again.&lt;/p&gt;
&lt;p&gt;Here are some more European myths starring this poor bird:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If a pregnant woman sees a barn owl and doesn&amp;rsquo;t kill it, her baby will die.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Whoever opens their mouth in front of a barn owl will lose their teeth.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Whoever lets a barn owl leave the house alive will lose a loved one.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Wow!
It&amp;rsquo;s just… not fair, poor thing!
Luckily, there are also cultures that see this animal differently.
In Hinduism, for example, the owl is seen as a symbol of wisdom and intelligence, representing the goddess &lt;a href="https://en.wikipedia.org/wiki/Lakshmi" rel="noopener" target="_blank"&gt;Lakshmi&lt;/a&gt;.
Anyway, this animal always keeps an aura of mystery: it is believed to be a messenger between the human and divine worlds.&lt;/p&gt;
&lt;p&gt;In conclusion, in the last centuries and millennia, the &lt;em&gt;inexplicable&lt;/em&gt; has often been linked with &lt;em&gt;mystery&lt;/em&gt;.
Since the animals and the forces of nature were the easier to observe, many civilizations have come to similar conclusions, creating akin myths and legends.
Only in the last decades, we&amp;rsquo;ve been able to understand that… back then, it must have been hard to be an animal.
Especially an owl.&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;In the Dordogne region, where the castle I visited lays, until 1929, wolves were considered ferocious beasts: lively, intelligent and the only predators in permanent contact with humans.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>Godsip Club</author><pubDate>Fri, 27 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://godsip.club/articles/white-ladies/</guid></item><item><title>JavaScript regular expressions cheatsheet and examples</title><link>https://learnbyexample.github.io/javascript-regexp-cheatsheet/</link><description>&lt;p align="center"&gt;&lt;img alt="sample railroad diagram of a regexp" src="/images/books/js_regexp_example.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Above diagram created using &lt;a href="https://jex.im/regulex"&gt;Regulex&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;p&gt;This blog post gives an overview of regular expression syntax and features supported by JavaScript. Examples have been tested on the Chrome/Chromium console and includes features not available in other browsers and platforms. This post is an excerpt from my &lt;a href="https://github.com/learnbyexample/learn_js_regexp"&gt;Understanding JavaScript RegExp&lt;/a&gt; book.&lt;/p&gt;
&lt;h2 id="elements-that-define-a-regular-expression"&gt;Elements that define a regular expression&lt;a class="zola-anchor" href="#elements-that-define-a-regular-expression"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Note&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions"&gt;MDN: Regular Expressions&lt;/a&gt;&lt;/td&gt;&lt;td&gt;MDN reference for JavaScript regular expressions&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;/pat/&lt;/code&gt;&lt;/td&gt;&lt;td&gt;a RegExp object&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;const pet = /dog/&lt;/code&gt;&lt;/td&gt;&lt;td&gt;save regexp in a variable for reuse, clarity, etc&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;/pat/.test(s)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;check if the pattern is present anywhere in the input string&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;returns &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;i&lt;/code&gt;&lt;/td&gt;&lt;td&gt;flag to ignore case when matching alphabets&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;g&lt;/code&gt;&lt;/td&gt;&lt;td&gt;flag to match all occurrences&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;new RegExp('pat', 'i')&lt;/code&gt;&lt;/td&gt;&lt;td&gt;construct RegExp from a string&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;optional second argument specifies flags&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;use backtick strings with &lt;code&gt;${}&lt;/code&gt; for interpolation&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;source&lt;/code&gt;&lt;/td&gt;&lt;td&gt;property to convert a RegExp object to a string&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;helps to insert a RegExp inside another RegExp&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;flags&lt;/code&gt;&lt;/td&gt;&lt;td&gt;property to get flags of a RegExp object&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;s.replace(/pat/, 'repl')&lt;/code&gt;&lt;/td&gt;&lt;td&gt;method for search and replace&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;s.search(/pat/)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;gives the starting location of the match or &lt;code&gt;-1&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;s.split(/pat/)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;split a string based on regexp&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Anchors&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;^&lt;/code&gt;&lt;/td&gt;&lt;td&gt;restricts the match to the start of string&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;$&lt;/code&gt;&lt;/td&gt;&lt;td&gt;restricts the match to the end of string&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;m&lt;/code&gt;&lt;/td&gt;&lt;td&gt;flag to match the start/end of line with &lt;code&gt;^&lt;/code&gt; and &lt;code&gt;$&lt;/code&gt; anchors&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;\r&lt;/code&gt;, &lt;code&gt;\n&lt;/code&gt;, &lt;code&gt;\u2028&lt;/code&gt; and &lt;code&gt;\u2029&lt;/code&gt; are line separators&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;DOS-style files use &lt;code&gt;\r\n&lt;/code&gt;, may need special attention&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\b&lt;/code&gt;&lt;/td&gt;&lt;td&gt;restricts the match to the start and end of words&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;word characters: alphabets, digits, underscore&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\B&lt;/code&gt;&lt;/td&gt;&lt;td&gt;matches wherever &lt;code&gt;\b&lt;/code&gt; doesn't match&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;code&gt;^&lt;/code&gt;, &lt;code&gt;$&lt;/code&gt; and &lt;code&gt;\&lt;/code&gt; are &lt;strong&gt;metacharacters&lt;/strong&gt; in the above table, as these characters have a special meaning. Prefix a &lt;code&gt;\&lt;/code&gt; character to remove the special meaning and match such characters literally. For example, &lt;code&gt;\^&lt;/code&gt; will match a &lt;code&gt;^&lt;/code&gt; character instead of acting as an anchor.&lt;/p&gt;
&lt;br /&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Feature&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;pat1|pat2|pat3&lt;/code&gt;&lt;/td&gt;&lt;td&gt;multiple regexp combined as conditional OR&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;each alternative can have independent anchors&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(pat)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;group pattern(s), also a capturing group&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;a(b|c)d&lt;/code&gt;&lt;/td&gt;&lt;td&gt;same as &lt;code&gt;abd|acd&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?:pat)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;non-capturing group&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?&amp;lt;name&amp;gt;pat)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;named capture group&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;.&lt;/code&gt;&lt;/td&gt;&lt;td&gt;match any character except line separators&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;s&lt;/code&gt;&lt;/td&gt;&lt;td&gt;flag to match line separators as well&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;[]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;character class, matches one character among many&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Alternation precedence: pattern which matches earliest in the input gets higher priority. Tie-breaker is left-to-right if matches have the same starting location.&lt;/p&gt;
&lt;br /&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Greedy Quantifiers&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;?&lt;/code&gt;&lt;/td&gt;&lt;td&gt;match &lt;code&gt;0&lt;/code&gt; or &lt;code&gt;1&lt;/code&gt; times&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;*&lt;/code&gt;&lt;/td&gt;&lt;td&gt;match &lt;code&gt;0&lt;/code&gt; or more times&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;+&lt;/code&gt;&lt;/td&gt;&lt;td&gt;match &lt;code&gt;1&lt;/code&gt; or more times&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;{m,n}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;match &lt;code&gt;m&lt;/code&gt; to &lt;code&gt;n&lt;/code&gt; times&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;{m,}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;match at least &lt;code&gt;m&lt;/code&gt; times&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;{n}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;match exactly &lt;code&gt;n&lt;/code&gt; times&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;pat1.*pat2&lt;/code&gt;&lt;/td&gt;&lt;td&gt;any number of characters between &lt;code&gt;pat1&lt;/code&gt; and &lt;code&gt;pat2&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;pat1.*pat2|pat2.*pat1&lt;/code&gt;&lt;/td&gt;&lt;td&gt;match both &lt;code&gt;pat1&lt;/code&gt; and &lt;code&gt;pat2&lt;/code&gt; in any order&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Greedy&lt;/strong&gt; here means that the above quantifiers will match as much as possible that'll also honor the overall regexp. Appending a &lt;code&gt;?&lt;/code&gt; to greedy quantifiers makes them &lt;strong&gt;non-greedy&lt;/strong&gt;, i.e. match as &lt;em&gt;minimally&lt;/em&gt; as possible. Quantifiers can be applied to literal characters, groups, backreferences and character classes.&lt;/p&gt;
&lt;br /&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Character class&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;[ae;o]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;match any of these characters once&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;[3-7]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;range of characters from &lt;code&gt;3&lt;/code&gt; to &lt;code&gt;7&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;[^=b2]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;negated set, match other than &lt;code&gt;=&lt;/code&gt; or &lt;code&gt;b&lt;/code&gt; or &lt;code&gt;2&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;[a-z-]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;-&lt;/code&gt; should be the first/last or escaped using &lt;code&gt;\&lt;/code&gt; to match literally&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;[+^]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;^&lt;/code&gt; shouldn't be the first character or escaped using &lt;code&gt;\&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;[\]\\]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;]&lt;/code&gt; and &lt;code&gt;\&lt;/code&gt; should be escaped using &lt;code&gt;\&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[&lt;/code&gt; doesn't need escaping, but &lt;code&gt;\[&lt;/code&gt; can also be used&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\w&lt;/code&gt;&lt;/td&gt;&lt;td&gt;similar to &lt;code&gt;[A-Za-z0-9_]&lt;/code&gt; for matching word characters&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\d&lt;/code&gt;&lt;/td&gt;&lt;td&gt;similar to &lt;code&gt;[0-9]&lt;/code&gt; for matching digit characters&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\s&lt;/code&gt;&lt;/td&gt;&lt;td&gt;similar to &lt;code&gt;[ \t\n\r\f\v]&lt;/code&gt; for matching whitespace characters&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;use &lt;code&gt;\W&lt;/code&gt;, &lt;code&gt;\D&lt;/code&gt;, and &lt;code&gt;\S&lt;/code&gt; for their opposites respectively&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;u&lt;/code&gt;&lt;/td&gt;&lt;td&gt;flag to enable unicode matching&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;v&lt;/code&gt;&lt;/td&gt;&lt;td&gt;superset of &lt;code&gt;u&lt;/code&gt; flag, enables additional features&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\p{}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Unicode character sets&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\P{}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;negated Unicode character sets&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;see &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Unicode_character_class_escape"&gt;MDN: Unicode character class escape&lt;/a&gt; for details&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\u{}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;specify Unicode characters using codepoints&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Lookarounds&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;lookarounds&lt;/td&gt;&lt;td&gt;create custom positive/negative assertions&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;zero-width like anchors and not part of matching portions&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?!pat)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;negative lookahead assertion&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?&amp;lt;!pat)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;negative lookbehind assertion&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?=pat)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;positive lookahead assertion&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?&amp;lt;=pat)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;positive lookbehind assertion&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;variable length lookbehind is allowed&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?!pat1)(?=pat2)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;multiple assertions can be specified next to each other in any order&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;as they mark a matching location without consuming characters&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;((?!pat).)*&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Negates a regexp pattern&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Matched portion&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;m = s.match(/pat/)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;assuming the &lt;code&gt;g&lt;/code&gt; flag isn't used and regexp succeeds,&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;returns an array with the matched portion and 3 properties&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;index&lt;/code&gt; property gives the starting location of the match&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;input&lt;/code&gt; property gives the input string &lt;code&gt;s&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;groups&lt;/code&gt; property gives dictionary of named capture groups&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;m[0]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;for the above case, gives the entire matched portion&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;m[N]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;matched portion of the Nth capture group&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;d&lt;/code&gt;&lt;/td&gt;&lt;td&gt;flag to get the starting and ending locations of the matching portions via the &lt;code&gt;indices&lt;/code&gt; property&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;s.match(/pat/g)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;returns only the matched portions, no properties&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;s.matchAll(/pat/g)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;returns an iterator containing details for each matched portion and its properties&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Backreference&lt;/td&gt;&lt;td&gt;gives the matched portion of the Nth capture group&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;use &lt;code&gt;$1&lt;/code&gt;, &lt;code&gt;$2&lt;/code&gt;, &lt;code&gt;$3&lt;/code&gt;, etc in the replacement section&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;$&amp;amp;&lt;/code&gt; gives the entire matched portion&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;$`&lt;/code&gt; gives the string before the matched portion&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;$'&lt;/code&gt; gives the string after the matched portion&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;use &lt;code&gt;\1&lt;/code&gt;, &lt;code&gt;\2&lt;/code&gt;, &lt;code&gt;\3&lt;/code&gt;, etc within the regexp definition&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;$$&lt;/code&gt;&lt;/td&gt;&lt;td&gt;insert &lt;code&gt;$&lt;/code&gt; literally in the replacement section&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;$0N&lt;/code&gt;&lt;/td&gt;&lt;td&gt;same as &lt;code&gt;$N&lt;/code&gt;, allows to separate backreference and other digits&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\N\xhh&lt;/code&gt;&lt;/td&gt;&lt;td&gt;allows to separate backreference and digits in the regexp definition&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?&amp;lt;name&amp;gt;pat)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;named capture group&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;use &lt;code&gt;\k&amp;lt;name&amp;gt;&lt;/code&gt; for backreferencing in the regexp definition&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;use &lt;code&gt;$&amp;lt;name&amp;gt;&lt;/code&gt; for backreferencing in the replacement section&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
&lt;h2 id="regular-expression-examples"&gt;Regular expression examples&lt;a class="zola-anchor" href="#regular-expression-examples"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;test()&lt;/code&gt; method&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ts " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ts"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #668f14;"&gt;let &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sentence &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'This is a sample string'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/is/&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;test&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sentence&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;true
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/xyz/&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;test&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sentence&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;false
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; if &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/ring/&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;test&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sentence&lt;/span&gt;&lt;span&gt;)) {
&lt;/span&gt;&lt;span&gt;      &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'mission success'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;mission success
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;new RegExp()&lt;/code&gt; constructor&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ts " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ts"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; new &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;RegExp&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'dog'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'i'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/dog/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;i
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; new &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;RegExp&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'123&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\\&lt;/span&gt;&lt;span style="color: #d07711;"&gt;tabc'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/123&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\t&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;abc/
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #668f14;"&gt;let &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;greeting &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'hi'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; new &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;RegExp&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;`${&lt;/span&gt;&lt;span style="color: #acb3c2;"&gt;greeting&lt;/span&gt;&lt;span style="color: #d07711;"&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;toUpperCase&lt;/span&gt;&lt;span style="color: #d07711;"&gt;()} there`&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/HI there/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;string and line anchors&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ts " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ts"&gt;&lt;span style="color: #7f8989;"&gt;// string anchors
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;cat/&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;test&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'cater'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;true
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'surrender'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'newer'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'door'&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span style="color: #c23f31;"&gt;filter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;w &lt;/span&gt;&lt;span style="color: #668f14;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/er&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;$&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;test&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;w&lt;/span&gt;&lt;span&gt;))
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'surrender'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'newer'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// use 'm' flag to match at the start/end of each line
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;par&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;$&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;m&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;test&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'spare&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;par&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;era&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;dare'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;true
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// escape metacharacters to match them literally
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/b&lt;/span&gt;&lt;span style="color: #108f3d;"&gt;\^&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;2/&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;test&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'a^2 + b^2 - C*3'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;true
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;replace()&lt;/code&gt; method and word boundaries&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ts " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ts"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #668f14;"&gt;let &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;items &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'catapults&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;concatenate&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;cat'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;items&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;gm&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'* '&lt;/span&gt;&lt;span&gt;))
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; * &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;catapults
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;* &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;concatenate
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;* &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;cat
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #668f14;"&gt;let &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sample &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'par spar apparent spare part'
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// replace 'par' only at the start of word
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sample&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;par/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'X'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'X spar apparent spare Xt'
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// replace 'par' at the end of word but not whole word 'par'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sample&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\B&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;par&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'X'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'par sX apparent spare part'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;alternations and grouping&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ts " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ts"&gt;&lt;span style="color: #7f8989;"&gt;// replace either 'cat' at the start of string or 'cat' at the end of word
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'catapults concatenate cat scat'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;cat&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;cat&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'X'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Xapults concatenate X sX'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// same as: /\bpark\b|\bpart\b/g
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'park parked part party'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;par(k&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;t)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'X'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'X parked X party'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping"&gt;MDN: Regular Expressions Guide&lt;/a&gt; provides the &lt;code&gt;escapeRegExp()&lt;/code&gt; function, useful to automatically escape metacharacters.
&lt;ul&gt;
&lt;li&gt;See also &lt;a href="https://github.com/slevithan/xregexp"&gt;XRegExp&lt;/a&gt;, provides handy methods like &lt;a href="https://xregexp.com/api/#escape"&gt;XRegExp.escape()&lt;/a&gt; and &lt;a href="https://xregexp.com/api/#union"&gt;XRegExp.union()&lt;/a&gt;. The union method has additional functionality of allowing a mix of string and RegExp literals and also takes care of renumbering backreferences.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ts " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ts"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #668f14;"&gt;function &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;escapeRegExp&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;string&lt;/span&gt;&lt;span&gt;) {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;return &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;string&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;.&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;*+?^${}()|[&lt;/span&gt;&lt;span style="color: #108f3d;"&gt;\]\\&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;]&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\\&lt;/span&gt;&lt;span style="color: #d07711;"&gt;$&amp;amp;'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #668f14;"&gt;function &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;unionRegExp&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;arr&lt;/span&gt;&lt;span&gt;) {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;return &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;arr&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #c23f31;"&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;w &lt;/span&gt;&lt;span style="color: #668f14;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;escapeRegExp&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;w&lt;/span&gt;&lt;span&gt;)).&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;join&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'|'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; new &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;RegExp&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c23f31;"&gt;unionRegExp&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'c^t'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'dog$'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'f|x'&lt;/span&gt;&lt;span&gt;]), &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'g'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/c&lt;/span&gt;&lt;span style="color: #108f3d;"&gt;\^&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;t&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;dog&lt;/span&gt;&lt;span style="color: #108f3d;"&gt;\$&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;f&lt;/span&gt;&lt;span style="color: #108f3d;"&gt;\|&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;x/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;dot metacharacter and quantifiers&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ts " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ts"&gt;&lt;span style="color: #7f8989;"&gt;// matches character '2', any character and then character '3'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'42&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\t&lt;/span&gt;&lt;span style="color: #d07711;"&gt;35'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/2&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;3/&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'8'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'485'
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// 's' flag will allow line separators to be matched as well
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Hi there&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;Have a Nice Day'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/the&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;ice/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;s&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'X'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Hi X Day'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// same as: /part|parrot|parent/g
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'par part parrot parent'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/par(en&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;ro)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;?&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;t/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'X'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'par X X X'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'abc'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'ac'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'abbc'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'xabbbcz'&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span style="color: #c23f31;"&gt;filter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;w &lt;/span&gt;&lt;span style="color: #668f14;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/ab&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;{1,4}&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;c/&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;test&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;w&lt;/span&gt;&lt;span&gt;))
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'abc'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'abbc'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'xabbbcz'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;match()&lt;/code&gt; method&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ts " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ts"&gt;&lt;span style="color: #7f8989;"&gt;// entire matched portion
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'too soon a song snatch'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/so&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;n/&lt;/span&gt;&lt;span&gt;)[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'soon'
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// matched portion of the second capture group
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #668f14;"&gt;let &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;purchase &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'coffee:100g tea:250g sugar:75g chocolate:50g'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;purchase&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/:(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)g&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;:(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)g&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;chocolate:(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)g/&lt;/span&gt;&lt;span&gt;)[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'250'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// starting location of the matching portion
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'cat and dog'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/dog/&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span style="color: #a2a001;"&gt;index
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;8
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// start and end+1 location of the matching portion
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'awesome'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;so&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;d&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;indices&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;5&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// get all matching portions with 'g' flag
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// no properties or group portions
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'par spar apparent spare part'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;s&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;?&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;par&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[et]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'spare'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'part'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// useful for debugging purposes as well
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'green:3.14:teal::brown:oh!:blue'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/:&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;:/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;':3.14:'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'::'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;':oh!:'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;matchAll()&lt;/code&gt; method&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ts " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ts"&gt;&lt;span style="color: #7f8989;"&gt;// same as: match(/so*n/g)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;Array&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #c23f31;"&gt;from&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'song too soon snatch'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #c23f31;"&gt;matchAll&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/so&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;n/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;m &lt;/span&gt;&lt;span style="color: #668f14;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;m&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;])
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'son'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'soon'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'sn'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// get the starting index for each match
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;Array&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #c23f31;"&gt;from&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'song too soon snatch'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #c23f31;"&gt;matchAll&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/so&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;n/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;m &lt;/span&gt;&lt;span style="color: #668f14;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;m&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #a2a001;"&gt;index&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;9&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;14&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// get only the capture group portions as an array for each match
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;Array&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #c23f31;"&gt;from&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'2023/04,1986/Mar,'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #c23f31;"&gt;matchAll&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)&lt;/span&gt;&lt;span style="color: #108f3d;"&gt;\/&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;),/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;m &lt;/span&gt;&lt;span style="color: #668f14;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;m&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;slice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;))
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;) [&lt;/span&gt;&lt;span style="color: #a2a001;"&gt;Array&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;Array&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;)]
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;: (&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;) [&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'2023'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'04'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;: (&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;) [&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1986'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Mar'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;length&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2
&lt;/span&gt;&lt;span&gt;  [[&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Prototype&lt;/span&gt;&lt;span&gt;]]: &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;Array&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;function/dictionary in the replacement section&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ts " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ts"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #668f14;"&gt;function &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;titleCase&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;m&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;g1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;g2&lt;/span&gt;&lt;span&gt;) {
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;return &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;g1&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;toUpperCase&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+ &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;g2&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;toLowerCase&lt;/span&gt;&lt;span&gt;()
&lt;/span&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'aBc ac ADC aBbBC'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/(a)(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;c)/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;ig&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;titleCase&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Abc Ac Adc Abbbc'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1 42 317'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;m &lt;/span&gt;&lt;span style="color: #668f14;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;m&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'2 84 634'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #668f14;"&gt;let &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;swap &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'cat'&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'tiger'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'tiger'&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'cat' &lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'cat tiger dog tiger cat'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/cat&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;tiger/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;k &lt;/span&gt;&lt;span style="color: #668f14;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;swap&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;k&lt;/span&gt;&lt;span&gt;])
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'tiger cat dog cat tiger'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;split()&lt;/code&gt; method&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ts " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ts"&gt;&lt;span style="color: #7f8989;"&gt;// split based on one or more digit characters
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Sample123string42with777numbers'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Sample'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'string'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'with'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'numbers'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// include the portion that caused the split as well
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Sample123string42with777numbers'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)/&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Sample'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'123'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'string'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'42'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'with'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'777'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'numbers'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// split based on digit or whitespace characters
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'**1&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\f&lt;/span&gt;&lt;span style="color: #d07711;"&gt;2&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;3star&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\t&lt;/span&gt;&lt;span style="color: #d07711;"&gt;7 77&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;**'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;\d\s&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'**'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'star'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'**'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// use non-capturing group if capturing is not needed
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'123handed42handy777handful500'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/hand(?:y&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;ful)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;?&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'123'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'ed42'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'777'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'500'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;backreferencing with normal/non-capturing/named capture groups&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ts " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ts"&gt;&lt;span style="color: #7f8989;"&gt;// remove any number of consecutive duplicate words separated by space
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// use \W+ instead of space to cover cases like 'a;a&amp;lt;-;a'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'aa a a a 42 f_1 f_1 f_13.14'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\w&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)( &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\1&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+\b&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'$1'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'aa a 42 f_1 f_13.14'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// add something around the entire matched portion
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'52 apples and 31 mangoes'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'($&amp;amp;)'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'(52) apples and (31) mangoes'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// duplicate the first field and add it as the last field
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fork,42,nice,3.14'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/,&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'$&amp;amp;,$`'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fork,42,nice,3.14,fork'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// use non-capturing groups when backreferencing isn't needed
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1,2,3,4,5,6,7'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;((?:&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;,]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;,)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;{3}&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;,]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)/&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'$1($2)'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1,2,3,(4),5,6,7'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// named capture groups, same as: replace(/(\w+),(\w+)/g, '$2,$1')
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'good,bad 42,24 x,y'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/(?&amp;lt;&lt;/span&gt;&lt;span style="color: #acb3c2;"&gt;fw&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\w&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;),(?&amp;lt;&lt;/span&gt;&lt;span style="color: #acb3c2;"&gt;sw&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\w&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'$&amp;lt;sw&amp;gt;,$&amp;lt;fw&amp;gt;'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'bad,good 24,42 y,x'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;examples for lookarounds&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ts " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ts"&gt;&lt;span style="color: #7f8989;"&gt;// change 'cat' only if it is not followed by a digit character
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// note that the end of string satisfies the given assertion
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// 'catcat' has two matches as the assertion doesn't consume characters
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'hey cats! cat42 cat_5 catcat'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/cat(?!&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'dog'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'hey dogs! cat42 dog_5 dogdog'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// change whole word only if it is not preceded by : or --
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;':cart apple --rest ;tea'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/(?&amp;lt;!:&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;--)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\w&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'X'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;':cart X --rest ;X'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// extract digits only if it is preceded by - and followed by ; or :
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'42 apple-5, fig3; x-83, y-20: f12'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/(?&amp;lt;=-)&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;(?=&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[;:]&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'20'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// words containing all lowercase vowels in any order
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #668f14;"&gt;let &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;words &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'sequoia'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'questionable'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'exhibit'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'equation'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;words&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #c23f31;"&gt;filter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;w &lt;/span&gt;&lt;span style="color: #668f14;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/(?=&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;a)(?=&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;e)(?=&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;i)(?=&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;o)&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;u/&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;test&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;w&lt;/span&gt;&lt;span&gt;))
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'sequoia'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'questionable'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'equation'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// replace only the third occurrence of 'cat'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'cat scatter cater scat'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/(?&amp;lt;=(cat&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;{2}&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)cat/&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'X'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'cat scatter Xer scat'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// match if 'do' is not there between 'at' and 'par'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/at((?!do)&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;par/&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;test&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fox,cat,dog,parrot'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;false
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;u&lt;/code&gt; and &lt;code&gt;v&lt;/code&gt; flags&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ts " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ts"&gt;&lt;span style="color: #7f8989;"&gt;// extract all consecutive letters, use \P{L} to invert the set
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fox:αλεπού,eagle:αετός'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #108f3d;"&gt;\p&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;{L}&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;gu&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fox'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'αλεπού'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'eagle'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'αετός'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// extract all consecutive Greek letters
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fox:αλεπού,eagle:αετός'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #108f3d;"&gt;\p&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;{sc=Greek}&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;gu&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'αλεπού'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'αετός'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// extract whole words not surrounded by punctuation marks
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'tie. ink east;'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/(?&amp;lt;!&lt;/span&gt;&lt;span style="color: #108f3d;"&gt;\p&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;{P})&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\w&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+\b&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;(?!&lt;/span&gt;&lt;span style="color: #108f3d;"&gt;\p&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;{P})/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;gu&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'ink'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// remove all punctuation characters except . ! and ?
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #668f14;"&gt;let &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;para &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&amp;quot;Hi&amp;quot;, there! How *are* you? All fine here.'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;para&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;[\&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;p&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;P&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;--&lt;/span&gt;&lt;span&gt;[.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;!?&lt;/span&gt;&lt;span&gt;]]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+/&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;gv&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;''&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Hi there! How are you? All fine here.'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="debugging-and-visualization-tools"&gt;Debugging and Visualization tools&lt;a class="zola-anchor" href="#debugging-and-visualization-tools"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As your regexp gets complicated, it can get difficult to debug when you run into issues. Building your regexp step by step from scratch and testing against input strings will go a long way in correcting the problem. To aid in such a process, you could use &lt;a href="https://news.ycombinator.com/item?id=20614847"&gt;various online regexp tools&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://regex101.com/r/HSeO0z/1"&gt;regex101&lt;/a&gt; is a popular site to test your regexp. You'll have to first choose the flavor as JavaScript. Then you can add your regexp, input strings, choose flags and an optional replacement string. Matching portions will be highlighted and explanation is offered in separate panes. There's also a quick reference and other features like link sharing, code generator, quiz, cheatsheet, etc.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="regex101 example" src="/images/books/regex101.png" /&gt;&lt;/p&gt;
&lt;p&gt;Another useful tool is &lt;a href="https://jex.im/regulex/#!flags=&amp;amp;re=%5Cbpar(en%7Cro)%3Ft%5Cb"&gt;jex: regulex&lt;/a&gt; which converts your regexp to a railroad diagram, thus providing a visual aid to understanding the pattern.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="regulex example" src="/images/books/regulex.png" /&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="understanding-javascript-regexp-book"&gt;Understanding JavaScript RegExp book&lt;a class="zola-anchor" href="#understanding-javascript-regexp-book"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Visit my repo &lt;a href="https://github.com/learnbyexample/learn_js_regexp"&gt;learn_js_regexp&lt;/a&gt; for details about the book I wrote on JavaScript regular expressions. The ebook uses plenty of examples to explain the concepts from the basics and includes &lt;a href="https://github.com/learnbyexample/learn_js_regexp/blob/master/Exercises.md"&gt;exercises&lt;/a&gt; to test your understanding. The cheatsheet and examples presented in this post are based on the contents of this book.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Understanding JavaScript RegExp cover image" height="360px" src="https://raw.githubusercontent.com/learnbyexample/learn_js_regexp/master/images/js_regexp_ls.png" width="640px" /&gt;&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Fri, 27 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/javascript-regexp-cheatsheet/</guid></item><item><title>Quality software deserves your hard‑earned cash</title><link>https://stephango.com/quality-software</link><description>&lt;p&gt;Quality software from independent makers is like quality food from the farmer’s market. A jar of handmade organic jam is not the same as mass-produced corn syrup-laden jam from the supermarket.&lt;/p&gt;

&lt;p&gt;Industrial fruit jam is filled with cheap ingredients and shelf stabilizers. Industrial software is filled with privacy-invasive trackers and proprietary formats.&lt;/p&gt;

&lt;p&gt;Google, Apple, and Microsoft make industrial software. Like industrial jam, industrial software has its benefits — it’s cheap, fairly reliable, widely available, and often gets the job done.&lt;/p&gt;

&lt;p&gt;Big tech companies earn hundreds of billions of dollars and employ hundreds of thousands of people. When they make a new app, they can market it to their billions of customers easily. They have unbeatable leverage over the cost of developing and maintaining their apps.&lt;/p&gt;

&lt;p&gt;Independent software makers are small teams that don’t have those economies of scale. They can try to compete on price by compromising their craft, or they can charge a fair price knowing this will drive a large number of people to choose big tech instead. Either way, big tech wins because they take a 20–30% cut of the app store money earned by most independent makers. A cost that the big tech companies do not incur.&lt;/p&gt;

&lt;p&gt;Big tech companies have the ability to make their software cheap by subsidizing costs in a variety of ways:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Google sells highly profitable advertising and makes its apps free, but you are subjected to ads and privacy-invasive tracking.&lt;/li&gt;
  &lt;li&gt;Apple sells highly profitable devices and makes its apps free, but locks you into a proprietary ecosystem.&lt;/li&gt;
  &lt;li&gt;Microsoft sells highly profitable enterprise contracts using a bundling strategy, and makes its apps cheap, also locking you into a proprietary ecosystem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some tech companies raise hundreds of millions of dollars from &lt;a class="internal-link" href="/vcware"&gt;venture capital investors&lt;/a&gt;, and use this money to subsidize pricing — until the money runs out, and the quality soon declines.&lt;/p&gt;

&lt;p&gt;I’m not saying these companies are evil. But their subsidies create the illusion that all software should be cheap or free.&lt;/p&gt;

&lt;p&gt;Industrial software has become so incredibly cheap that most of us have lost the sense for how much value a quality piece of software can provide. We have become numb to the taste of good software and hypnotized by the idea of “free”.&lt;/p&gt;

&lt;p&gt;I’m not sure why, but we seem more willing to spend money on good fruit jam than on good software. I notice that I spend less on personal software than I do on groceries and many basic things. Yet software is one of the few things I pay for that truly gives me leverage. Consider its &lt;a class="internal-link" href="/buy-wisely"&gt;cost per use&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Independent makers of quality software go out of their way to make apps that are &lt;em&gt;better for you&lt;/em&gt;. They take a principled approach to making tools that don’t compromise your privacy, and &lt;a class="internal-link" href="/file-over-app"&gt;don’t lock you in&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Independent software makers are people you can talk to. Like quality jam from the farmer’s market, you might become friends with the person who made it — they’ll listen to your suggestions and your complaints.&lt;/p&gt;

&lt;p&gt;If you want to live in a world with more than a handful of software makers, then spend a bit more on quality independent software. It deserves your hard-earned cash.&lt;/p&gt;</description><author>Steph Ango</author><pubDate>Fri, 27 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://stephango.com/quality-software</guid></item><item><title>Perfect Project Management with Linear</title><link>https://xavd.id/blog/post/perfect-project-management-with-linear/</link><description>Building my perfect project management workflow with Linear and Python&lt;br /&gt;&lt;br /&gt;&lt;a href="https://xavd.id/blog/post/perfect-project-management-with-linear/"&gt;Read the whole thing&lt;/a&gt;.</description><author>The David Brownman Blog</author><pubDate>Fri, 27 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xavd.id/blog/post/perfect-project-management-with-linear/</guid></item><item><title>What 5 years of interviewing software engineers taught me</title><link>https://www.lasselaursen.com/post/what-5-years-of-interviewing-software-engineers-taught-me/</link><description>From 2017 to 2023 I interviewed and evaluated over 100 C++ software engineering candidates, with the final three years also covering Python. In this article I&amp;#8217;ll cover key lessons I learned from this experience, including: The TL/DR for the time poor reader: Test your software engineering candidates in the exact types of problems they&amp;#8217;re likely [&amp;#8230;]</description><author>Lasse Laursen</author><pubDate>Thu, 26 Oct 2023 09:57:09 GMT</pubDate><guid isPermaLink="true">https://www.lasselaursen.com/post/what-5-years-of-interviewing-software-engineers-taught-me/</guid></item><item><title>2023-10-26-001</title><link>https://srijan.ch/notes/2023-10-26-001</link><description>Test webmention + fed.brid.gy: Two naked tags walk into a bar. The bartender exclaims, "Hey, you can't come in here without microformats, this is a classy joint!"</description><author>Srijan Choudhary, all posts</author><pubDate>Thu, 26 Oct 2023 09:05:00 GMT</pubDate><guid isPermaLink="true">https://srijan.ch/notes/2023-10-26-001</guid></item><item><title>Written in Dvorak</title><link>https://blog.imraniqbal.org/dvorak/</link><description>&lt;p&gt;These past weeks I have been learning to touch type Dvorak.
I have been using the lovely tool &lt;a href="https://www.gnu.org/software/gtypist/" rel="external"&gt;&lt;code&gt;gtypist&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I wrote this entire post using the layout.
My typing speed took a considerable hit.
I average about 80 words per minute with QWERTY, but now I am down to 15.
Here's to hoping that it gets better with practice.&lt;/p&gt;
&lt;p&gt;Also this is messing with my vim muscle memory.
This took a considerable time to type out.&lt;/p&gt;</description><author>Imran's Blog</author><pubDate>Thu, 26 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.imraniqbal.org/dvorak/</guid></item><item><title>Redundant Information</title><link>https://backendhance.com/blog/2023/redundant-information/</link><description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;Nach dem Import packen wir das File aus und lesen die Meta-Informationen&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;Und wo schreiben wir sie rein&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;Diese Information wird in unterschiedlichen Datenbanken abgelegt. Die Path Information wird auf allen Service Datenbanken verteilt, damit diese wissen wie sie auf die Files zugreifen. Andere Daten legen wir in die System-Datenbank und in jede Tenant-Datenbank.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Klingt das für dich normal?&lt;/p&gt;
&lt;p&gt;Sollte es nicht.&lt;/p&gt;
&lt;h1 id="was-wir-hier-erleben-ist-informationsduplizierung"&gt;
&lt;a class="anchor" href="#was-wir-hier-erleben-ist-informationsduplizierung"&gt;Was wir hier erleben ist Informationsduplizierung.&lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;Aber Marcus, warum ist das denn so schlimm? Wir brauchen die Information halt an unterschiedlichen stellen.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;</description><author>Backendhance</author><pubDate>Thu, 26 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/redundant-information/</guid></item><item><title>2023-10-26</title><link>https://ho.dges.online/pictures/2023-10-26/</link><description/><author>ho.dges.online</author><pubDate>Thu, 26 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ho.dges.online/pictures/2023-10-26/</guid></item><item><title>Understanding JavaScript RegExp book announcement</title><link>https://learnbyexample.github.io/understanding-javascript-regexp-announcement/</link><description>&lt;p&gt;Hello!&lt;/p&gt;
&lt;p&gt;I just published a new version of &amp;quot;&lt;strong&gt;Understanding JavaScript RegExp&lt;/strong&gt;&amp;quot; ebook. Added examples for &lt;code&gt;d&lt;/code&gt; and &lt;code&gt;v&lt;/code&gt; flags, corrected many mistakes, improved examples, exercises and so on.&lt;/p&gt;
&lt;p&gt;This book will help you learn &lt;strong&gt;JavaScript Regular Expressions&lt;/strong&gt; step-by-step from beginner to advanced levels with &lt;strong&gt;hundreds of examples and exercises&lt;/strong&gt;.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="release-offers"&gt;Release offers&lt;a class="zola-anchor" href="#release-offers"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To celebrate the new release, you can download PDF/EPUB versions of &lt;strong&gt;Understanding JavaScript RegExp&lt;/strong&gt; for FREE till 05-Nov-2023. You can still pay if you wish ;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/js_regexp"&gt;Gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/js_regexp/c/new_js_regexp_release"&gt;Leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;All Books Bundle&lt;/strong&gt; is just $12 (normal price $32) — includes all my 13 programming ebooks.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/all-books/new_js_regexp_release"&gt;Gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/b/learnbyexample-all-books/c/new_js_regexp_release"&gt;Leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="what-s-new"&gt;What's new?&lt;a class="zola-anchor" href="#what-s-new"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Examples and exercises added for &lt;code&gt;d&lt;/code&gt; and &lt;code&gt;v&lt;/code&gt; flags&lt;/li&gt;
&lt;li&gt;Strings in code snippets changed to be uniformly represented in single quotes&lt;/li&gt;
&lt;li&gt;In general, many of the examples, exercises, solutions, descriptions and external links were updated/corrected&lt;/li&gt;
&lt;li&gt;Updated Acknowledgements section&lt;/li&gt;
&lt;li&gt;Code snippets related to info/warning sections will now appear as a single block&lt;/li&gt;
&lt;li&gt;Book title changed to &lt;strong&gt;Understanding JavaScript RegExp&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;New cover image&lt;/li&gt;
&lt;li&gt;Images centered for EPUB format&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="videos"&gt;Videos&lt;a class="zola-anchor" href="#videos"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;p&gt;On this blog, I &lt;a href="https://learnbyexample.github.io/tips/"&gt;post tips&lt;/a&gt; covering Python, command line tools and Vim. Here are video demos for these tips:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=THSMmCZQn1A&amp;amp;list=PLTv2U3HnAL4PlFDiH3FXTHXRbhWs2sB3F"&gt;Python tips&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=p0KCLusMd5Q&amp;amp;list=PLTv2U3HnAL4PNTmRqZBSUgKaiHbRL2zeY"&gt;Linux command line tips&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="testimonials"&gt;Testimonials&lt;a class="zola-anchor" href="#testimonials"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Literally was having a mini-breakdown about not understanding Regex in algorithm solutions the other day and now I'm feeling so much better, so thank YOU! I genuinely feel like I'm developing the skill for spotting when and where to use them after so much practice!&lt;/p&gt;
&lt;p&gt;— &lt;a href="https://twitter.com/codingwithlucy/status/1450668315635036160"&gt;feedback on twitter&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;a class="zola-anchor" href="#table-of-contents"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Preface&lt;/li&gt;
&lt;li&gt;Why is it needed?&lt;/li&gt;
&lt;li&gt;RegExp introduction&lt;/li&gt;
&lt;li&gt;Anchors&lt;/li&gt;
&lt;li&gt;Alternation and Grouping&lt;/li&gt;
&lt;li&gt;Escaping metacharacters&lt;/li&gt;
&lt;li&gt;Dot metacharacter and Quantifiers&lt;/li&gt;
&lt;li&gt;Interlude: Tools for debugging and visualization&lt;/li&gt;
&lt;li&gt;Working with matched portions&lt;/li&gt;
&lt;li&gt;Character class&lt;/li&gt;
&lt;li&gt;Groupings and backreferences&lt;/li&gt;
&lt;li&gt;Interlude: Common tasks&lt;/li&gt;
&lt;li&gt;Lookarounds&lt;/li&gt;
&lt;li&gt;Unicode&lt;/li&gt;
&lt;li&gt;Further Reading&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;h2 id="web-version"&gt;Web version&lt;a class="zola-anchor" href="#web-version"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can also read the book online here: &lt;a href="https://learnbyexample.github.io/learn_js_regexp/"&gt;https://learnbyexample.github.io/learn_js_regexp/&lt;/a&gt;.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="github-repo"&gt;GitHub repo&lt;a class="zola-anchor" href="#github-repo"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Visit &lt;a href="https://github.com/learnbyexample/learn_js_regexp"&gt;https://github.com/learnbyexample/learn_js_regexp&lt;/a&gt; for markdown source, exercise solutions, sample chapters and other details related to the book.&lt;/p&gt;
&lt;p&gt;See &lt;a href="https://learnbyexample.github.io/customizing-pandoc/"&gt;my blog post&lt;/a&gt; on how to customize &lt;code&gt;pandoc&lt;/code&gt; for generating beautiful PDF/EPUB versions from GitHub style markdown.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="newsletter"&gt;Newsletter&lt;a class="zola-anchor" href="#newsletter"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Subscribe to &lt;a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly"&gt;learnbyexample weekly&lt;/a&gt; — free newsletter covering programming resources, updates on what I am creating, tools, free ebooks and more, delivered every Friday.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="feedback-and-errata"&gt;Feedback and Errata&lt;a class="zola-anchor" href="#feedback-and-errata"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I would highly appreciate if you'd &lt;strong&gt;let me know how you felt about this book&lt;/strong&gt;. It could be anything from a simple thank you, Gumroad rating, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.&lt;/p&gt;
&lt;p&gt;You can reach me via:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Issue Manager: &lt;a href="https://github.com/learnbyexample/learn_js_regexp/issues"&gt;https://github.com/learnbyexample/learn_js_regexp/issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;E-mail: &lt;code&gt;learn by example.net@gmail.com&lt;/code&gt; (without the spaces)&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/learn_byexample"&gt;https://twitter.com/learn_byexample&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Thu, 26 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/understanding-javascript-regexp-announcement/</guid></item><item><title>Hacking on Neovim plugins</title><link>https://swaits.com/hacking-out-neovim-plugins/</link><description>&lt;p&gt;I'm having fun. It may seem mindless, pointless, or even counter-productive.
But it's not. It's fun!&lt;/p&gt;
&lt;p&gt;It all started with me optimizing my &lt;code&gt;nvim&lt;/code&gt; start times. Though I used &lt;code&gt;vi&lt;/code&gt; or
&lt;code&gt;vim&lt;/code&gt; basically forever, and used &lt;code&gt;nvim&lt;/code&gt; since it started in 2014, I never
really worried about optimizing startup times. But, truth is, &lt;code&gt;nvim&lt;/code&gt; is doing
&lt;em&gt;much more&lt;/em&gt; today than it ever did in the past, so maybe performance is more
critical now.&lt;/p&gt;
&lt;p&gt;I already used &lt;a href="https://www.lazyvim.org/" rel="external"&gt;LazyVim&lt;/a&gt; as my starting point. Now I
wanted to trim down the number of plugins I was using, maximize my lazy
loading, and generally streamline my configuration. And that led me into
hacking out a few really simple plugins.&lt;/p&gt;
&lt;p&gt;But before I dive into the plugin stuff, I want to talk a bit about why I chose
LazyVim awhile ago.&lt;/p&gt;
&lt;h4 id="contents"&gt;Contents&lt;/h4&gt;
&lt;!--toc:start--&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://swaits.com/hacking-out-neovim-plugins/#lazyvim"&gt;LazyVim?&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://swaits.com/hacking-out-neovim-plugins/#optimizing-startup"&gt;Optimizing Startup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/hacking-out-neovim-plugins/#results"&gt;Results&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/hacking-out-neovim-plugins/#writing-plugins"&gt;Writing Plugins&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://swaits.com/hacking-out-neovim-plugins/#thethethe-nvim"&gt;thethethe.nvim&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/hacking-out-neovim-plugins/#zellij-nav-nvim"&gt;zellij-nav.nvim&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/hacking-out-neovim-plugins/#scratch-nvim"&gt;scratch.nvim&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swaits.com/hacking-out-neovim-plugins/#closing"&gt;Closing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;!--toc:end--&gt;
&lt;h2 id="lazyvim"&gt;LazyVim?&lt;/h2&gt;
&lt;p&gt;LazyVim is a pre-configured &lt;code&gt;nvim&lt;/code&gt; setup based on the &lt;a href="https://github.com/folke/lazy.nvim" rel="external"&gt;lazy.vim&lt;/a&gt; plugin manager.&lt;/p&gt;
&lt;p&gt;I use LazyVim because it does most of the complex configuration for me, and yet
it's still easily customizable. So, out of the box, I get an awesome
configuration for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;all the languages I need (primarily rust and tex)&lt;/li&gt;
&lt;li&gt;a full debugger interface&lt;/li&gt;
&lt;li&gt;automatic testing&lt;/li&gt;
&lt;li&gt;LSP support, auto-completion, snippets&lt;/li&gt;
&lt;li&gt;Codeium completions&lt;/li&gt;
&lt;li&gt;formatting&lt;/li&gt;
&lt;li&gt;linting&lt;/li&gt;
&lt;li&gt;really nice UI additions and tweaks&lt;/li&gt;
&lt;li&gt;it's fast!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In other words, it's actually a true IDE. To me "true" here means it must
include a debugger, at minimum.&lt;/p&gt;
&lt;p&gt;And, you don't have to use all that. It's easy to configure. If you don't need
something, you just turn it off. If something's missing, you can add it.
Overall, it's the best &lt;code&gt;nvim&lt;/code&gt; plugin manager and default configuration I've
used.&lt;/p&gt;
&lt;h3 id="optimizing-startup"&gt;Optimizing Startup&lt;/h3&gt;
&lt;p&gt;I always felt like my &lt;code&gt;nvim&lt;/code&gt; loaded quickly enough. I didn't &lt;strong&gt;need&lt;/strong&gt; to
optimize anything. But then I thought, "hey, I'm not really taking advantage of
&lt;code&gt;lazy.vim&lt;/code&gt;'s lazy loading support here", and figured I'd see how far I could
push it.&lt;/p&gt;
&lt;p&gt;Most of my startup optimization came from configuring as many of my custom
loaded plugins as possible to load lazily. This consists of setting &lt;code&gt;lazy = true&lt;/code&gt; and then either specifying the event to keystrokes to load on. For
example, here's how I'm loading
&lt;a href="https://github.com/chrisgrieser/nvim-origami" rel="external"&gt;nvim-origami&lt;/a&gt; and
&lt;a href="https://github.com/anuvyklack/pretty-fold.nvim" rel="external"&gt;pretty-fold.nvim&lt;/a&gt;, so they
won't load until &lt;code&gt;nvim&lt;/code&gt; fires the &lt;code&gt;BufReadPost&lt;/code&gt; event.&lt;/p&gt;
&lt;pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #AAD94C;"&gt;    &amp;quot;chrisgrieser/nvim-origami&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    dependencies&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;nvim-treesitter/nvim-treesitter&amp;quot; &lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    lazy&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt; true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    event&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt; &amp;quot;BufReadPost&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;-- later or on keypress would prevent saving folds&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FFB454;"&gt;    init&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #FF8F40;"&gt; function&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;      vim.opt.foldenable&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;      vim.opt.foldmethod&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt; &amp;quot;expr&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;      vim.opt.foldexpr&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt; &amp;quot;nvim_treesitter#foldexpr()&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;      vim.opt.foldnestmax&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt; 3&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;      vim.opt.foldminlines&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;    end&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    opts&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span&gt; {},&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #AAD94C;"&gt;    &amp;quot;anuvyklack/pretty-fold.nvim&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    lazy&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt; true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    event&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt; &amp;quot;BufReadPost&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    opts&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span&gt; {},&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here's how I load &lt;a href="https://github.com/duff/vim-scratch" rel="external"&gt;vim-scratch&lt;/a&gt; when the
&lt;code&gt;nvim&lt;/code&gt; command &lt;code&gt;Scratch&lt;/code&gt; is called:&lt;/p&gt;
&lt;pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #AAD94C;"&gt;  &amp;quot;duff/vim-scratch&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  lazy&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt; true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  cmd&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt; &amp;quot;Scratch&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And finally, here's how I make &lt;a href="https://yazi-rs.github.io/" rel="external"&gt;yazi&lt;/a&gt; load via
&lt;a href="https://github.com/DreamMaoMao/yazi.nvim" rel="external"&gt;yazi.nvim&lt;/a&gt; on a keystroke:&lt;/p&gt;
&lt;pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #AAD94C;"&gt;  &amp;quot;DreamMaoMao/yazi.nvim&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  lazy&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt; true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  dependencies&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #AAD94C;"&gt;    &amp;quot;nvim-telescope/telescope.nvim&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #AAD94C;"&gt;    &amp;quot;nvim-lua/plenary.nvim&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  keys&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    { &lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;&amp;lt;leader&amp;gt;fy&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;&amp;lt;cmd&amp;gt;Yazi&amp;lt;CR&amp;gt;&amp;quot;&lt;/span&gt;&lt;span&gt;, desc&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt; &amp;quot;Toggle Yazi&amp;quot; &lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="results"&gt;Results&lt;/h3&gt;
&lt;p&gt;After trimming out lots of unnecessary plugins and making as many plugins as
possible load lazily, my &lt;code&gt;nvim&lt;/code&gt; starts in under 90ms now (see screenshot
below). Before this it was in the 250ms-300ms range.&lt;/p&gt;
&lt;p&gt;As I said above, I don't &lt;strong&gt;need&lt;/strong&gt; this. But it's very pleasing.&lt;/p&gt;
&lt;p&gt;&lt;img alt="my nvim start menu" src="https://swaits.com/hacking-out-neovim-plugins/nvim-alpha.png" /&gt;&lt;/p&gt;
&lt;h2 id="writing-plugins"&gt;Writing Plugins&lt;/h2&gt;
&lt;p&gt;As I went through all the plugins I was using, I evaluated not only whether I
really needed them, but also whether they were developed for &lt;code&gt;nvim&lt;/code&gt; (instead of
just &lt;code&gt;vim&lt;/code&gt;) and played nicely with lazy loading.&lt;/p&gt;
&lt;h3 id="thethethe-nvim"&gt;&lt;a href="https://git.sr.ht/~swaits/thethethe.nvim" rel="external"&gt;thethethe.nvim&lt;/a&gt; &lt;img alt="" src="https://swaits.com/hacking-out-neovim-plugins/#thethethe-nvim" /&gt;&lt;/h3&gt;
&lt;p&gt;One of those plugins was
&lt;a href="https://github.com/panozzaj/vim-autocorrect" rel="external"&gt;vim-autocorrect&lt;/a&gt;. This is a
plugin that automatically corrects common typos like "teh" into "the" by use of
&lt;code&gt;vim&lt;/code&gt; abbreviations (&lt;code&gt;iabbrev&lt;/code&gt;). But, it was designed for &lt;code&gt;vim&lt;/code&gt; and written in
&lt;code&gt;vimscript&lt;/code&gt;. I wanted something in &lt;code&gt;lua&lt;/code&gt; and specific to &lt;code&gt;nvim&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;And so, &lt;a href="https://git.sr.ht/~swaits/thethethe.nvim" rel="external"&gt;thethethe.nvim&lt;/a&gt; was born. I
converted the dictionary to &lt;code&gt;lua&lt;/code&gt; and deferred its loading, so it wouldn't delay
startup.&lt;/p&gt;
&lt;p&gt;Now the dictionary looks like this:&lt;/p&gt;
&lt;pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;return&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt; [[&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #AAD94C;"&gt;adn:and&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #AAD94C;"&gt;teh:the&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #AAD94C;"&gt;-- ... thousands more words here&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #AAD94C;"&gt;]]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the initialization code, I wrote a function to load this dictionary into
&lt;code&gt;iabbrev&lt;/code&gt;s, which I'm able to defer calling like this:&lt;/p&gt;
&lt;pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;-- execute the function after config.delay_ms&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;vim.&lt;/span&gt;&lt;span style="color: #F07178;"&gt;defer_fn&lt;/span&gt;&lt;span&gt;(load_abbreviations, config.delay_ms)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At the end, I can lazy load my own plugin like this:&lt;/p&gt;
&lt;pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #AAD94C;"&gt;  &amp;quot;https://git.sr.ht/~swaits/thethethe.nvim&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  lazy&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt; true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  event&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt; &amp;quot;VeryLazy&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  opts&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span&gt; {},&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Neat!&lt;/p&gt;
&lt;h3 id="zellij-nav-nvim"&gt;&lt;a href="https://git.sr.ht/~swaits/zellij-nav.nvim" rel="external"&gt;zellij-nav.nvim&lt;/a&gt; &lt;img alt="" src="https://swaits.com/hacking-out-neovim-plugins/#zellij-nav-nvim" /&gt;&lt;/h3&gt;
&lt;p&gt;Next up, in the process of switching from &lt;a href="https://github.com/tmux/tmux" rel="external"&gt;tmux&lt;/a&gt;
to &lt;a href="https://zellij.dev/" rel="external"&gt;zellij&lt;/a&gt;, I wanted a way to easily move both between
windows inside &lt;code&gt;nvim&lt;/code&gt;, and then also move from an &lt;code&gt;nvim&lt;/code&gt; pane in zellij to
another zellij pane. I found a few plugins doing this, but I found them to be
needlessly complex.&lt;/p&gt;
&lt;p&gt;So I set out to create the smallest, simplest thing that worked. I ended up
with 43 whole lines of &lt;code&gt;lua&lt;/code&gt; code. Yes, this is the entire plugin. It's short
enough to include here in its entirety:&lt;/p&gt;
&lt;pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;local&lt;/span&gt;&lt;span&gt; M&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span&gt; {}&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;local function&lt;/span&gt;&lt;span style="color: #FFB454;"&gt; nav&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt;(short_direction&lt;/span&gt;&lt;span style="color: #BFBDB6B3;"&gt;,&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt; direction)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;  -- get window ID, try switching windows, and get ID again to see if it worked&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;  local&lt;/span&gt;&lt;span&gt; cur_winnr&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span&gt; vim.fn.&lt;/span&gt;&lt;span style="color: #F07178;"&gt;winnr&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  vim.api.&lt;/span&gt;&lt;span style="color: #F07178;"&gt;nvim_command&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;wincmd &amp;quot; &lt;/span&gt;&lt;span style="color: #F29668;"&gt;..&lt;/span&gt;&lt;span&gt; short_direction)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;  local&lt;/span&gt;&lt;span&gt; new_winnr&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span&gt; vim.fn.&lt;/span&gt;&lt;span style="color: #F07178;"&gt;winnr&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;  -- if the window ID didn't change, then we didn't switch&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;  if&lt;/span&gt;&lt;span&gt; cur_winnr&lt;/span&gt;&lt;span style="color: #F29668;"&gt; ==&lt;/span&gt;&lt;span&gt; new_winnr&lt;/span&gt;&lt;span style="color: #FF8F40;"&gt; then&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    vim.fn.&lt;/span&gt;&lt;span style="color: #F07178;"&gt;system&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;zellij action move-focus &amp;quot; &lt;/span&gt;&lt;span style="color: #F29668;"&gt;..&lt;/span&gt;&lt;span&gt; direction)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;    if&lt;/span&gt;&lt;span&gt; vim.v.shell_error&lt;/span&gt;&lt;span style="color: #F29668;"&gt; ~=&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt; 0&lt;/span&gt;&lt;span style="color: #FF8F40;"&gt; then&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #F07178;"&gt;      error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;zellij executable not found in path&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;function&lt;/span&gt;&lt;span style="color: #FFB454;"&gt; M&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #FFB454;"&gt;up&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #F07178;"&gt;  nav&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;k&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;up&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;function&lt;/span&gt;&lt;span style="color: #FFB454;"&gt; M&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #FFB454;"&gt;down&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #F07178;"&gt;  nav&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;j&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;down&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;function&lt;/span&gt;&lt;span style="color: #FFB454;"&gt; M&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #FFB454;"&gt;right&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #F07178;"&gt;  nav&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;l&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;function&lt;/span&gt;&lt;span style="color: #FFB454;"&gt; M&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #FFB454;"&gt;left&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #F07178;"&gt;  nav&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;h&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;-- create our exported setup() function&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;function&lt;/span&gt;&lt;span style="color: #FFB454;"&gt; M&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #FFB454;"&gt;setup&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt;(opts)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #5A6673; font-style: italic;"&gt;  -- create our commands&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  vim.api.&lt;/span&gt;&lt;span style="color: #F07178;"&gt;nvim_create_user_command&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;ZellijNavigateUp&amp;quot;&lt;/span&gt;&lt;span&gt;, M.up, {})&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  vim.api.&lt;/span&gt;&lt;span style="color: #F07178;"&gt;nvim_create_user_command&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;ZellijNavigateDown&amp;quot;&lt;/span&gt;&lt;span&gt;, M.down, {})&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  vim.api.&lt;/span&gt;&lt;span style="color: #F07178;"&gt;nvim_create_user_command&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;ZellijNavigateLeft&amp;quot;&lt;/span&gt;&lt;span&gt;, M.left, {})&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  vim.api.&lt;/span&gt;&lt;span style="color: #F07178;"&gt;nvim_create_user_command&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;ZellijNavigateRight&amp;quot;&lt;/span&gt;&lt;span&gt;, M.right, {})&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;return&lt;/span&gt;&lt;span&gt; M&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And I can load it like this:&lt;/p&gt;
&lt;pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #AAD94C;"&gt;    &amp;quot;https://git.sr.ht/~swaits/zellij-nav.nvim&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    lazy&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt; true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    keys&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;      { &lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;&amp;lt;c-h&amp;gt;&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;&amp;lt;cmd&amp;gt;ZellijNavigateLeft&amp;lt;cr&amp;gt;&amp;quot;&lt;/span&gt;&lt;span&gt;, { silent&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt; true&lt;/span&gt;&lt;span&gt;, desc&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt; &amp;quot;navigate left&amp;quot; &lt;/span&gt;&lt;span&gt;} },&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;      { &lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;&amp;lt;c-j&amp;gt;&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;&amp;lt;cmd&amp;gt;ZellijNavigateDown&amp;lt;cr&amp;gt;&amp;quot;&lt;/span&gt;&lt;span&gt;, { silent&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt; true&lt;/span&gt;&lt;span&gt;, desc&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt; &amp;quot;navigate down&amp;quot; &lt;/span&gt;&lt;span&gt;} },&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;      { &lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;&amp;lt;c-k&amp;gt;&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;&amp;lt;cmd&amp;gt;ZellijNavigateUp&amp;lt;cr&amp;gt;&amp;quot;&lt;/span&gt;&lt;span&gt;, { silent&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt; true&lt;/span&gt;&lt;span&gt;, desc&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt; &amp;quot;navigate up&amp;quot; &lt;/span&gt;&lt;span&gt;} },&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;      { &lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;&amp;lt;c-l&amp;gt;&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;&amp;lt;cmd&amp;gt;ZellijNavigateRight&amp;lt;cr&amp;gt;&amp;quot;&lt;/span&gt;&lt;span&gt;, { silent&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt; true&lt;/span&gt;&lt;span&gt;, desc&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt; &amp;quot;navigate right&amp;quot; &lt;/span&gt;&lt;span&gt;} },&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    opts&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span&gt; {},&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So satisfying!&lt;/p&gt;
&lt;h3 id="scratch-nvim"&gt;&lt;a href="https://git.sr.ht/~swaits/scratch.nvim" rel="external"&gt;scratch.nvim&lt;/a&gt; &lt;img alt="" src="https://swaits.com/hacking-out-neovim-plugins/#scratch-nvim" /&gt;&lt;/h3&gt;
&lt;p&gt;Finally, a conversion of &lt;a href="https://github.com/duff/vim-scratch" rel="external"&gt;vim-scratch&lt;/a&gt;,
which hasn't been updated in over a decade, to lua.&lt;/p&gt;
&lt;p&gt;There are fancier scratch buffer plugins out there. But this one works exactly
the way I like. And now I have it in lua.&lt;/p&gt;
&lt;p&gt;This one shows one of the benefits of lua over vimscript -- that the
&lt;a href="https://git.sr.ht/~swaits/scratch.nvim/tree/main/item/lua/scratch/init.lua" rel="external"&gt;code&lt;/a&gt;
is much more understandable and easier to follow.&lt;/p&gt;
&lt;p&gt;I use it like this:&lt;/p&gt;
&lt;pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FF8F40;"&gt;return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #AAD94C;"&gt;  &amp;quot;https://git.sr.ht/~swaits/scratch.nvim&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  lazy&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #D2A6FF;"&gt; true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  keys&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    { &lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;&amp;lt;leader&amp;gt;bs&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;&amp;lt;cmd&amp;gt;Scratch&amp;lt;cr&amp;gt;&amp;quot;&lt;/span&gt;&lt;span&gt;, desc&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt; &amp;quot;Scratch Buffer&amp;quot;&lt;/span&gt;&lt;span&gt;, mode&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt; &amp;quot;n&amp;quot; &lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    { &lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;&amp;lt;leader&amp;gt;bS&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #AAD94C;"&gt;&amp;quot;&amp;lt;cmd&amp;gt;ScratchSplit&amp;lt;cr&amp;gt;&amp;quot;&lt;/span&gt;&lt;span&gt;, desc&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt; &amp;quot;Scratch Buffer (split)&amp;quot;&lt;/span&gt;&lt;span&gt;, mode&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span style="color: #AAD94C;"&gt; &amp;quot;n&amp;quot; &lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  cmd&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #AAD94C;"&gt;    &amp;quot;Scratch&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #AAD94C;"&gt;    &amp;quot;ScratchSplit&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;  opts&lt;/span&gt;&lt;span style="color: #F29668;"&gt; =&lt;/span&gt;&lt;span&gt; {},&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="closing"&gt;Closing&lt;/h2&gt;
&lt;p&gt;In the beginning, I said this was fun. That's so true; it was very fun.&lt;/p&gt;
&lt;p&gt;I also said it might seem pointless or even counter-productive. But was it?&lt;/p&gt;
&lt;p&gt;I don't think so.&lt;/p&gt;
&lt;p&gt;In the process of doing this, I learned some &lt;code&gt;lua&lt;/code&gt;, learned about &lt;code&gt;nvim&lt;/code&gt;
plugins, and streamlined my entire coding and writing environment. But those
are &lt;strong&gt;not&lt;/strong&gt; the reasons this was a valuable use of my time.&lt;/p&gt;
&lt;p&gt;It was valuable because it was fun!&lt;/p&gt;</description><author>swaits.com</author><pubDate>Thu, 26 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://swaits.com/hacking-out-neovim-plugins/</guid></item><item><title>Opportunity Solution Trees</title><link>http://evantravers.com/articles/2023/10/25/opportunity-solution-trees/</link><description>&lt;div class="admonition Summary"&gt;
&lt;p&gt;An expert Mental Model unpacking how an experienced Product Manager connects Solutions to Outcomes and prioritizes Experiments. The tree broadens the horizon past the first Solution found, compares Opportunity-connected Solutions, and prioritizes Experiments.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Opportunity Solution Tree" src="https://images.evantravers.com/articles/2023/10/ost.png" /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a class="anchor" href="#steps" id="steps"&gt;&lt;/a&gt;Steps&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Identify a clear &lt;code&gt;Desired Outcome&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Product &lt;code&gt;Opportunities&lt;/code&gt; to drive that outcome should emerge from user/market needs discovered during generative research.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Solutions&lt;/code&gt; come from everywhere… but they should connect to an &lt;code&gt;Opportunity&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Experiment&lt;/code&gt; to evaluate and evolve &lt;code&gt;Solutions&lt;/code&gt;.&lt;sup class="footnote-ref"&gt;&lt;a href="#fn-lean" id="fnref-lean"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;&lt;a class="anchor" href="#reframing-for-wide-discovery" id="reframing-for-wide-discovery"&gt;&lt;/a&gt;Reframing for Wide Discovery&lt;/h2&gt;
&lt;p&gt;Torres was inspired by Reframing Questions&lt;sup class="footnote-ref"&gt;&lt;a href="#fn-reframe" id="fnref-reframe"&gt;2&lt;/a&gt;&lt;/sup&gt; to broaden stakeholder's mindset with an additional logic layer of the &lt;code&gt;Opportunity&lt;/code&gt;. As humans we typically fixate on the first discovered &lt;code&gt;Solution&lt;/code&gt;, yet we actually want as &lt;em&gt;many&lt;/em&gt; to investigate and explore as possible.&lt;sup class="footnote-ref"&gt;&lt;a href="#fn-discovery" id="fnref-discovery"&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a class="anchor" href="#prioritization-through-connection" id="prioritization-through-connection"&gt;&lt;/a&gt;Prioritization through Connection&lt;/h2&gt;
&lt;p&gt;By tying the &lt;code&gt;Solution&lt;/code&gt; to an &lt;code&gt;Opportunity&lt;/code&gt; (the user problem it is attempting to solve) you can reason more clearly and prioritize ruthlessly.&lt;/p&gt;
&lt;p&gt;Attempting a broad &amp;quot;whether or not&amp;quot; decision on a particular &lt;code&gt;Solution&lt;/code&gt; can be a stalemate.&lt;sup class="footnote-ref"&gt;&lt;a href="#fn-1" id="fnref-1"&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Using a Opportunity Solution Tree:&lt;/p&gt;
&lt;p&gt;We can quickly discard &lt;code&gt;Solutions&lt;/code&gt; that aren't tied to an &lt;code&gt;Opportunity&lt;/code&gt; that drives our &lt;code&gt;Desired Outcome&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next we can bound our decision to &amp;quot;compare and contrast&amp;quot; &lt;code&gt;Solutions&lt;/code&gt; related to the same high-value and real &lt;code&gt;Opportunities&lt;/code&gt;. We then craft &lt;code&gt;Experiments&lt;/code&gt; to measure the effectiveness of these &lt;code&gt;Solutions&lt;/code&gt;, guided by the &lt;code&gt;Opportunity&lt;/code&gt;'s framing to narrow our measurements.&lt;/p&gt;
&lt;p&gt;Related:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Introduced to me by Dave Masom of &lt;a href="https://conserv.io"&gt;Conserv&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;From &lt;em&gt;Continuous Discovery Habits&lt;/em&gt; by Teresa Torres (&lt;a href="https://andrewclark.co.uk/product-book-summaries/continuous-discovery-habits"&gt;Amazing summary by Andrew Clark here&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Source: &lt;a href="https://evantravers.com/articles/2020/07/09/outcomes-over-outputs/"&gt;https://www.producttalk.org/opportunity-solution-tree/&lt;/a&gt; and a couple related blog posts and podcasts.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Desired Outcome&lt;/code&gt; -&amp;gt; &lt;code&gt;Outcomes&lt;/code&gt; :: &lt;code&gt;Solutions&lt;/code&gt; -&amp;gt; &lt;code&gt;Outputs&lt;/code&gt; (&lt;a href="https://evantravers.com/articles/2020/07/09/outcomes-over-outputs/"&gt;Outcomes over Outputs&lt;/a&gt;) Opportunity Solution Trees is a lens on these related concepts. The exercises and visuals can untangle an existing concept space that may be too solution heavy and needs a tie-breaker.&lt;/li&gt;
&lt;/ul&gt;
&lt;section class="footnotes"&gt;
&lt;ol&gt;
&lt;li id="fn-lean"&gt;
&lt;p&gt;It feels like &lt;a href="https://evantravers.com/articles/2019/07/31/books-and-links-july/#lean-ux-by-jeff-gothelf-and-josh-seiden"&gt;Lean UX by Jeff Gothelf&lt;/a&gt; would work well here. &lt;a class="footnote-backref" href="#fnref-lean"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-reframe"&gt;
&lt;p&gt;Bernie Roth used a powerful question to drive past Buyers are Liars to get at the truth. After asking people what they wanted, he asked&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you had whatever you wrote down today, what would that do for you?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;He drove past the expressed &lt;code&gt;Solution&lt;/code&gt; to discover the true &lt;code&gt;Desired Outcome&lt;/code&gt;. Exploring past the Output to determine the Outcome. (&lt;a href="https://evantravers.com/articles/2020/07/09/outcomes-over-outputs/"&gt;Outcomes over Outputs&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;He then broadened that initial &lt;code&gt;Solution&lt;/code&gt; by asking&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;How else might you {accomplish the same goal}?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This prevented being fixated on the first potential path and creating a tree of paths to explore. He was reframing the expressed &lt;code&gt;Solution&lt;/code&gt; desire into a &lt;code&gt;Desired Outcome&lt;/code&gt;… the truth of what the attendee was attempting to accomplish &lt;a class="footnote-backref" href="#fnref-reframe"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-discovery"&gt;
&lt;p&gt;Similar to Discovery step of Double Diamond Design Process. The Double Diamond Design Process describes a double sequence of Divergent and Convergent Thinking. It's common for people thinking about Service Design.&lt;/p&gt;
&lt;p&gt;&lt;img alt="double diamond" src="https://images.evantravers.com/articles/2023/10/double-diamond.png" /&gt;&lt;/p&gt;
&lt;p&gt;According to &lt;em&gt;Org Design for Design Orgs&lt;/em&gt; by Peter Merholz, Kristin Skinner, the Execution step is really a series of smaller Execution steps (iteration and implementation). How the Plan is framed determines how effective the Execution step will be. &lt;a class="footnote-backref" href="#fnref-discovery"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-1"&gt;
&lt;blockquote&gt;
&lt;p&gt;We can argue all day, but we won’t make any progress if we spend all of our time debating the merits of each solution.&lt;/p&gt;
&lt;p&gt;Instead, we want to consider which problem each solution solves and instead make a decision about which problem leads to a more valuable opportunity.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Source: &lt;a href="https://www.producttalk.org/2016/08/opportunity-solution-tree/"&gt;https://www.producttalk.org/2016/08/opportunity-solution-tree/&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</description><author>trv.rs</author><pubDate>Wed, 25 Oct 2023 13:05:00 GMT</pubDate><guid isPermaLink="true">http://evantravers.com/articles/2023/10/25/opportunity-solution-trees/</guid></item><item><title>The optimization treadmill: why I keep changing my computing setup all the time</title><link>https://ounapuu.ee/posts/2023/10/25/the-optimization-treadmill/</link><description>A short explanation about what drives me to experiment with new hardware setups in my self-hosting hobby.</description><author>./techtipsy</author><pubDate>Wed, 25 Oct 2023 10:00:00 GMT</pubDate><guid isPermaLink="true">https://ounapuu.ee/posts/2023/10/25/the-optimization-treadmill/</guid></item><item><title>2023-10-25-001</title><link>https://srijan.ch/notes/2023-10-25-001</link><description>Added an RSS feed for notes</description><author>Srijan Choudhary, all posts</author><pubDate>Wed, 25 Oct 2023 09:20:00 GMT</pubDate><guid isPermaLink="true">https://srijan.ch/notes/2023-10-25-001</guid></item><item><title>LINQ DistinctBy on a property for .NET Standard and older .NET versions</title><link>https://nicolaiarocci.com/linq-distinctby-on-a-property-for-dotnet-standard-and-old-dotnet-versions/</link><description>&lt;p&gt;Today I learned how to implement a custom &lt;code&gt;Enumerable.DistinctBy&lt;/code&gt; extension method that returns distinct elements from a
sequence according to a specified key selector function.&lt;/p&gt;
&lt;p&gt;.NET 6 and its successors have the method &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.distinctby?view=net-6.0"&gt;built in&lt;/a&gt; within LINQ, but I needed it in a .NET Standard 2.0 class
library, so I was out of luck. My implementation is simple, not different from &lt;a href="https://stackoverflow.com/a/489421/323269"&gt;others&lt;/a&gt; I found online, and should
also work fine with old .NET releases. Here it is:&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Wed, 25 Oct 2023 09:05:25 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/linq-distinctby-on-a-property-for-dotnet-standard-and-old-dotnet-versions/</guid></item><item><title>Population Count Pattern</title><link>https://denvaar.dev/posts/population_count_pattern.html</link><description>Understanding why a concise, macro-defined array can be used as a lookup table for finding the number of set bits in a number.</description><author>denvaar's website</author><pubDate>Wed, 25 Oct 2023 08:40:09 GMT</pubDate><guid isPermaLink="true">https://denvaar.dev/posts/population_count_pattern.html</guid></item><item><title>Fedora is pretty stable</title><link>https://seirdy.one/notes/2023/10/19/fedora-is-pretty-stable/</link><description>&lt;aside&gt;&lt;p&gt;
	Reply to &lt;span class="h-cite u-in-reply-to"&gt;&lt;cite class="p-name"&gt;&lt;a class="u-url" href="https://solarpunk.moe/@alilly/111264290797323783"&gt;Fedora is specifically not a stable distro, it's often considered the early-adopter distro.&lt;/a&gt;&lt;/cite&gt; by
		&lt;span class="h-card vcard p-author"&gt;&lt;a class="u-url url" href="https://alm.website/me"&gt;&lt;span class="p-name fn n"&gt;Athena L.M.&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;
	&lt;/span&gt;
&lt;/p&gt;&lt;/aside&gt;&lt;p&gt;Fedora is a stable distro now, with three levels of pre-release: Rawhide is unstable, Branched is &lt;em&gt;sort of&lt;/em&gt; like an alpha release, and Beta is for early adopters.&lt;/p&gt;&lt;p&gt;Fedora is just semi-rolling, with a combination of soft-frozen and rolling packages. Each release gets just over a year of support, so you can double-upgrade on alternate releases if you wish.&lt;/p&gt;&lt;p&gt;It’s obviously no Debian but it’s not the bleeding edge distro it once was. Each release has the latest toolchains and runtimes but it’s not generally as bleeding edge as e.g. Arch.&lt;/p&gt;&lt;p&gt;Strictly speaking, Fedora stable releases technically aren’t even upstream RHEL anymore; CentOS Stream pulls right from Rawhide.&lt;/p&gt;</description><author>All content on Seirdy’s Home</author><pubDate>Wed, 25 Oct 2023 08:38:31 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/notes/2023/10/19/fedora-is-pretty-stable/</guid></item><item><title>Hello World!</title><link>https://www.cpuonfire.com/blog/hello-world/</link><description>My blog is finally up and running. I will write about how I set this up and other details soon.</description><author>cpuonfire</author><pubDate>Wed, 25 Oct 2023 07:18:46 GMT</pubDate><guid isPermaLink="true">https://www.cpuonfire.com/blog/hello-world/</guid></item><item><title>Wash three walls with one bucket</title><link>https://zackproser.com/blog/wash-three-walls-with-one-bucket</link><description>Design your side projects, blog posts and even your fun experiments to triangulate multiple learning paths simultaneously. Then, use them to build out your portfolio.</description><author>Zachary Proser</author><pubDate>Wed, 25 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/wash-three-walls-with-one-bucket</guid></item><item><title>Visual content search over music videos - demo</title><link>https://sidsite.com/posts/video-search-demo/</link><description>Link to the demo.</description><author>sidsite</author><pubDate>Wed, 25 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://sidsite.com/posts/video-search-demo/</guid></item><item><title>Space Engineers ValidateAuthTicketResponse TicketCancelled</title><link>https://studiofreya.org/posts/space-engineers-validateauthticketresponse-ticketcancelled/</link><description>&lt;p&gt;I started playing Space Engineers again on a multiplayer server I host. Even though I played alone, occasionally I got disconnected every 5-20 minutes. There was no pattern to it, and there were really no good hits on DDG or Google.&lt;/p&gt;
&lt;p&gt;The server runs in Docker on a Linux host, I even rebuilt the image from scratch, and it didn&amp;rsquo;t help. I even ran the server on my local machine, where I&amp;rsquo;m running the game.&lt;/p&gt;
&lt;p&gt;The server logs said &lt;code&gt;ValidateAuthTicketResponse(TicketCancelled)&lt;/code&gt;, which is Steam&amp;rsquo;s way of authenticating and authorizing a player to play a game on a server. With &lt;code&gt;TicketCancelled&lt;/code&gt;, it means my credentials are not valid somehow, even though I just logged in.&lt;/p&gt;
&lt;p&gt;I even signed out and logged in to see if that got me fresh &amp;ldquo;tickets&amp;rdquo;, but no.&lt;/p&gt;
&lt;p&gt;Sometimes I play with remote play from a laptop, in this case a Mac with M2 processor. And any time used remote play on that, I never got disconnected!&lt;/p&gt;
&lt;p&gt;After some experimentation, I found that any time the Mac was sleeping, with Steam open in the background, I would get disconnected by irregular intervals. &lt;strong&gt;After I quit Steam on the laptop, the problem went away. I never got &lt;code&gt;TicketCancelled&lt;/code&gt; again&lt;/strong&gt;.&lt;/p&gt;</description><author>Studiofreya SSG Site</author><pubDate>Wed, 25 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://studiofreya.org/posts/space-engineers-validateauthticketresponse-ticketcancelled/</guid></item><item><title>About</title><link>https://www.cpuonfire.com/about/</link><description>I&amp;rsquo;m Sai Phanindra. Welcome to my cute little corner on the inter webs! I spend my time reading random wikipedia articles, self-hosting various software, mapping wherever I go on OpenStreetMap, and feeding kittens. I write about things I love and post some random photos here.
You can find me on Mastodon, OSM, LinkedIn, Twitter, and other places on the internet. Oh I also write FOSS Weekly.</description><author>cpuonfire</author><pubDate>Wed, 25 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.cpuonfire.com/about/</guid></item><item><title>Wiki Nature</title><link>https://one.mikro2nd.net/pages/wiki-nature/</link><description>WikiNature is found in linking. The very best wiki is one where pages cannot be created except by linking to the new, nonexistent page from an already-existing page. The newborn page is already linked, already has a relationship with other pages, just as a newborn babe comes into the world with relationships already in place.
And if links are fundamental, then automated backlinks become an essential affordance that must be immediately to hand, always present and visible and navigable, not hidden away in some folded-away footer, not requiring an action from me, the reader, to open them up.</description><author>one mikro2nd</author><pubDate>Wed, 25 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://one.mikro2nd.net/pages/wiki-nature/</guid></item><item><title>Golda</title><link>https://olshansky.info/movie/golda/</link><description>Olshansky's review of Golda</description><author>🦉 olshansky 🦁</author><pubDate>Tue, 24 Oct 2023 16:18:18 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/movie/golda/</guid></item><item><title>Grace and Frankie: Season 3</title><link>https://olshansky.info/tv/grace_and_frankie_season_3/</link><description>Olshansky's review of Grace and Frankie: Season 3</description><author>🦉 olshansky 🦁</author><pubDate>Tue, 24 Oct 2023 16:18:18 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/tv/grace_and_frankie_season_3/</guid></item><item><title>Open in Xcode at line number</title><link>https://xenodium.com/open-in-xcode-at-line-number</link><description>&lt;p&gt;I live mostly in Emacs. I say mostly 'cause well, I'm fairly pragmatic about it. If there's a workflow elsewhere that's more appropriate for my needs, I'll happily use that instead. While I'd love to do my web browsing from my beloved editor, Firefox ticks the right boxes for me.&lt;/p&gt;
&lt;p&gt;I do most of my iOS coding in Emacs. It's a hybrid of sorts between Emacs and Xcode. If I need to use the debugger, Xcode is a clear winner for me. If I happen to be visiting a Swift file in an Emacs buffer, I typically used the handy &lt;code&gt;crux-open-with&lt;/code&gt; from &lt;a href="https://github.com/bbatsov/crux"&gt;crux&lt;/a&gt; to open in Xcode, and continue from there. This worked OK, but I always wished opening in Xcode would also jump to the same line number as the Emacs point (cursor) location. This is particularly useful if I had just spotted where I'd like to set a breakpoint in an Emacs buffer and need to transition over to Xcode.&lt;/p&gt;
&lt;p&gt;It turns out, there's a nifty command line utility for that. &lt;a href="https://www.unix.com/man-page/osx/1/xed/"&gt;xed&lt;/a&gt;, the Xcode text editor invocation tool. It enables telling Xcode what file to open and at what line number:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.bash"&gt;xed -line 141 path/to/some/file.swift
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that in mind, I've added my own version of &lt;code&gt;crux-open-with&lt;/code&gt;, using &lt;a href="https://github.com/xenodium/dwim-shell-command"&gt;dwim-shell-command&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When running on macOS, the function checks whether or not I'm visiting a buffer for a programming language, and opens the file in Xcode at the same line number.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(defun dwim-shell-commands-open-externally ()
  &amp;quot;Open file(s) externally.&amp;quot;
  (interactive)
  (dwim-shell-command-on-marked-files
   &amp;quot;Open externally&amp;quot;
   (if (eq system-type 'darwin)
       (if (derived-mode-p 'prog-mode)
           (format &amp;quot;xed --line %d '&amp;lt;&amp;lt;f&amp;gt;&amp;gt;'&amp;quot;
                   (line-number-at-pos (point)))
         &amp;quot;open '&amp;lt;&amp;lt;f&amp;gt;&amp;gt;'&amp;quot;)
     &amp;quot;xdg-open '&amp;lt;&amp;lt;f&amp;gt;&amp;gt;'&amp;quot;)
   :shell-args '(&amp;quot;-x&amp;quot; &amp;quot;-c&amp;quot;)
   :silent-success t
   :utils (if (eq system-type 'darwin)
              &amp;quot;open&amp;quot;
            &amp;quot;xdg-open&amp;quot;)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/open-in-xcode-at-line-number/xed_x0.8_x2.webp" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dwim-shell-commands-open-externally&lt;/code&gt; is now &lt;a href="https://github.com/xenodium/dwim-shell-command/commit/19be1c2f3792c95f04fd369cb931a52f7df9cfd5"&gt;added to dwim-shell-commands.el&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;ps. If you find opening the same file in a different context handy, you may also like the package &lt;a href="https://github.com/rmuslimov/browse-at-remote"&gt;browse-at-remote&lt;/a&gt; that opens the visited file at its corresponding remote location (for example, GitHub). I can never remember the name of the function (&lt;a href="https://github.com/rmuslimov/browse-at-remote"&gt;browse-at-remote&lt;/a&gt;), so I aliased it to something I'd remember and moved on…&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(defalias 'ar/open-at-github #'browse-at-remote))
&lt;/code&gt;&lt;/pre&gt;</description><author>xenodium.com @alvaro</author><pubDate>Tue, 24 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/open-in-xcode-at-line-number</guid></item><item><title>Kenmore Dryer Repair</title><link>http://pxtl.ca/2023/10/24/dryer-repair/</link><description>&lt;p&gt;Once again, everything is made of garbage.&lt;/p&gt;

&lt;p&gt;&lt;img alt="failed heating coil in dryer" src="http://pxtl.ca/images/2023-10-24-dryer-repair/failed-coil.jpg" /&gt;&lt;/p&gt;</description><author>Pxtl.ca</author><pubDate>Tue, 24 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">http://pxtl.ca/2023/10/24/dryer-repair/</guid></item><item><title>Rate Throttling in Django AllAuth while running Unit Tests</title><link>https://banagale.com/rate-throttling-in-django-allauth-while-running-unit-tests.htm</link><description>If you use Django AllAuth and have a custom account adapter, you may find you run up to against rate limits that prevent successful testing of the many unhappy paths of user signup workflows. allauth.account.adapter.DefaultAccountAdapter I did not notice this until I overrode the clean_email() method of my custom adapter, after noticing some unhandled pentest&amp;#8230; &lt;a class="more-link" href="https://banagale.com/rate-throttling-in-django-allauth-while-running-unit-tests.htm"&gt;Continue reading &lt;span class="screen-reader-text"&gt;Rate Throttling in Django AllAuth while running Unit Tests&lt;/span&gt;&lt;/a&gt;</description><author>Rob</author><pubDate>Tue, 24 Oct 2023 01:17:00 GMT</pubDate><guid isPermaLink="true">https://banagale.com/rate-throttling-in-django-allauth-while-running-unit-tests.htm</guid></item><item><title>Help! I’m stuck! I want to get to the next level!!</title><link>https://abdulapopoola.com/2023/10/23/help-im-stuck-i-want-to-get-to-the-next-level/</link><description>This article clarifies how promotions work and highlights potential pitfalls. It describes a career-growth framework based on technical skills, interpersonal relationships, and influence. As one becomes more senior, the balance between these skills shifts, demanding more focus on relationships and influence.</description><author>CodeKraft</author><pubDate>Mon, 23 Oct 2023 17:00:00 GMT</pubDate><guid isPermaLink="true">https://abdulapopoola.com/2023/10/23/help-im-stuck-i-want-to-get-to-the-next-level/</guid></item><item><title>The virtue of selective ignorance and why I finally switched to WordPress</title><link>https://www.lasselaursen.com/post/the-virtue-of-selective-ignorance-and-why-i-finally-switched-to-wordpress/</link><description>This post is the second in a three-part series about migrating from a self-built PHP-based Laravel backend to a managed WordPress backend, in an attempt to minimize on-going maintenance and custom code. Part one discussed why moving to WordPress felt like a visit to the Ferengi homeworld, and the final part of the trilogy will [&amp;#8230;]</description><author>Lasse Laursen</author><pubDate>Mon, 23 Oct 2023 15:11:19 GMT</pubDate><guid isPermaLink="true">https://www.lasselaursen.com/post/the-virtue-of-selective-ignorance-and-why-i-finally-switched-to-wordpress/</guid></item><item><title>The Photo Game: Metaphor</title><link>https://andrewbridges.org/the-photo-game-metaphor/</link><description>I almost failed this week. On Friday, I had only shot a handful of pictures on a 36-frame roll of 35mm film, and I was thinking about quitting the Photo Game.</description><author>Show Notes</author><pubDate>Mon, 23 Oct 2023 05:53:39 GMT</pubDate><guid isPermaLink="true">https://andrewbridges.org/the-photo-game-metaphor/</guid></item><item><title>GCC Compiler vs Human - 119x Faster Assembly 💻🆚🧑‍💻</title><link>https://ashvardanian.com/posts/gcc-12-vs-avx512fp16/</link><description>Hand-written AVX-512FP16 SIMD code achieves 119x speedup over GCC's auto-vectorization for Jensen-Shannon divergence, using reciprocal square roots and FMA instructions.</description><author>Ash's Blog</author><pubDate>Mon, 23 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ashvardanian.com/posts/gcc-12-vs-avx512fp16/</guid></item><item><title>Broken Windows</title><link>https://backendhance.com/blog/2023/broken-windows/</link><description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;p&gt;Vor einer Woche, in meinem Talk &lt;a href="https://www.linkedin.com/posts/marcus-held_pragmaticprogramming-codingtips-softwaredevelopment-activity-7119941419745599488-lqC7" rel="noopener noreferrer" target="_blank"&gt;&amp;ldquo;Pragmatic Programming mit Kotlin&amp;rdquo;&lt;i class="bx bx-link-external"&gt;&lt;/i&gt;&lt;/a&gt;
, habe ich über &lt;strong&gt;die Broken Window Theory&lt;/strong&gt; gesprochen.&lt;/p&gt;
&lt;p&gt;Eigentlich kommt sie aus der Kriminologie.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In criminology, the broken windows theory states that visible signs of crime, anti-social behavior and civil disorder [&amp;hellip;] encourages further crime and disorder [&amp;hellip;].&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;~ &lt;a href="https://en.wikipedia.org/wiki/Broken_windows_theory" rel="noopener noreferrer" target="_blank"&gt;Wikipedia&lt;i class="bx bx-link-external"&gt;&lt;/i&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;Dave Thomas und Andy Hunt übertragen dieses Bild in die Softwareentwicklung.&lt;/p&gt;
&lt;p&gt;Wann immer es ein schlechtes Design, eine unübersichtliche Klasse, schlecht getesteten Code oder ungenutzte Assets im Repository gibt, wird es wahrscheinlicher, dass noch weitere &amp;ldquo;broken windows&amp;rdquo; folgen.&lt;/p&gt;</description><author>Backendhance</author><pubDate>Mon, 23 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/broken-windows/</guid></item><item><title>Unpacking some Rust ergonomics: getting a single Result from an iterator of them</title><link>https://ntietz.com/blog/rust-vec-of-result/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;Rust has a lot of nice things that make life easy.
One of the least discussed ones is also one of my favorites.
It's a little nugget in the standard library that makes handling possible failures a lot easier.
And it's not even baked in—it just falls out from the type system.&lt;/p&gt;
&lt;h1 id="nicely-handling-multiple-results-or-options"&gt;Nicely handling multiple &lt;code&gt;Result&lt;/code&gt;s or &lt;code&gt;Option&lt;/code&gt;s&lt;/h1&gt;
&lt;p&gt;When you do something that can fail, you get back a type that reflects that.
You'll get either a &lt;code&gt;Result&amp;lt;T, E&amp;gt;&lt;/code&gt; or an &lt;code&gt;Option&amp;lt;T&amp;gt;&lt;/code&gt;, depending on if it's something that could fail or could just not be present.
When you work in Rust, you end up getting &lt;em&gt;very&lt;/em&gt; comfortable with these types, and there are a lot of ergonomics to help you.&lt;/p&gt;
&lt;p&gt;One of those bits of ergonomics that I love is how you can collect an iterable of &lt;code&gt;Results&lt;/code&gt; into a &lt;code&gt;Result&lt;/code&gt; of a &lt;code&gt;Vec&lt;/code&gt;, effectively flipping the result inside out: you would expect a &lt;code&gt;Vec&amp;lt;Result&amp;lt;T, E&amp;gt;&amp;gt;&lt;/code&gt;, and you can get a &lt;code&gt;Result&amp;lt;Vec&amp;lt;T&amp;gt;, E&amp;gt;&lt;/code&gt; instead!
The same thing applies for &lt;code&gt;Option&lt;/code&gt;.
Let's see it in action.&lt;/p&gt;
&lt;p&gt;Suppose you have a function which could fail, and you call it a number of times.
Something like this:&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #fa5c4b;"&gt;fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;fetch_chunk&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;from&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;usize&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;to&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;usize&lt;/span&gt;&lt;span&gt;) -&amp;gt; &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Result&lt;/span&gt;&lt;span&gt;&amp;lt;Row, Error&amp;gt; {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-style: italic; color: #928374;"&gt;// some implementation
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When we call it, and if we collect directly, we get a bunch of &lt;code&gt;Result&lt;/code&gt;s:&lt;/p&gt;
&lt;pre style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code&gt;&lt;span&gt;let chunks: Vec&amp;lt;Result&amp;lt;Row, Error&amp;gt;&amp;gt; =
&lt;/span&gt;&lt;span&gt;    indexes.iter().map(|i| fetch_chunk(i, i+1)).collect();
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now this is kind of ugly to deal with.
In a lot of cases, it &lt;em&gt;is&lt;/em&gt; the type you want, because you can see which operations failed&lt;sup class="footnote-reference"&gt;
  &lt;a href="https://ntietz.com/blog/rust-vec-of-result/#fn-1" id="fn-ref-1"&gt;1&lt;/a&gt;
&lt;/sup&gt;
.
But sometimes, you just want to know if &lt;em&gt;anything&lt;/em&gt; failed, and in that case you can collect directly into a &lt;code&gt;Result&lt;/code&gt;.&lt;/p&gt;
&lt;pre style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code&gt;&lt;span&gt;let chunks: Result&amp;lt;Vec&amp;lt;Row&amp;gt;, Error&amp;gt; =
&lt;/span&gt;&lt;span&gt;    indexes.iter().map(|i| fetch_chunk(i, i+1)).collect();
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the same code with a different type signature, and it collects into a &lt;em&gt;different type&lt;/em&gt;.
That's pretty darn cool, if you ask me.
Just by which type you ask for, you get that one back!&lt;/p&gt;
&lt;p&gt;This pattern of pulling the Result from the inside to the outside is one that's present in functional programming languages.
I was trying to find a name for it, and the closest parallel we&lt;sup class="footnote-reference"&gt;
  &lt;a href="https://ntietz.com/blog/rust-vec-of-result/#fn-2" id="fn-ref-2"&gt;2&lt;/a&gt;
&lt;/sup&gt;
 found was Haskell's &lt;a href="https://hackage.haskell.org/package/base-4.19.0.0/docs/Control-Monad.html#v:sequence"&gt;&lt;code&gt;sequence&lt;/code&gt;&lt;/a&gt;, which is somewhat unsatisfying in the end since it feels like there should be a name for the &lt;em&gt;concept&lt;/em&gt; of this pulling the result type from the inside to the outside.&lt;/p&gt;
&lt;p&gt;You can do other nice things in a similar way here.&lt;/p&gt;
&lt;h1 id="how-it-works"&gt;How it works&lt;/h1&gt;
&lt;p&gt;Under the hood, there's no magic here.
This isn't built into Rust.
It's just part of the standard library, and you can implement things like that for your own types!&lt;/p&gt;
&lt;p&gt;&lt;code&gt;collect&lt;/code&gt; is the method where the magic happens.
It's a very general method on &lt;a href="https://doc.rust-lang.org/std/iter/trait.Iterator.html"&gt;iterators&lt;/a&gt;, with this type from &lt;a href="https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect"&gt;the docs&lt;/a&gt;:&lt;/p&gt;
&lt;pre style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code&gt;&lt;span&gt;fn collect&amp;lt;B&amp;gt;(self) -&amp;gt; B
&lt;/span&gt;&lt;span&gt;where
&lt;/span&gt;&lt;span&gt;    B: FromIterator&amp;lt;Self::Item&amp;gt;,
&lt;/span&gt;&lt;span&gt;    Self: Sized,
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is basically saying that for any type that implements &lt;code&gt;FromIterator&lt;/code&gt; for the type that this iterator yields, you can collect it into that type.
An easy example is how an iterator with &lt;code&gt;Item = i32&lt;/code&gt; can be used to collect into a &lt;code&gt;Vec&amp;lt;i32&amp;gt;&lt;/code&gt;, since &lt;code&gt;Vec&lt;/code&gt; implements &lt;code&gt;FromIterator&lt;/code&gt; for all types.&lt;/p&gt;
&lt;p&gt;And then the magic is these two impls:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://doc.rust-lang.org/std/result/enum.Result.html#impl-FromIterator%3CResult%3CA,+E%3E%3E-for-Result%3CV,+E%3E"&gt;&lt;code&gt;FromIterator&amp;lt;Result&amp;lt;A, E&amp;gt;&amp;gt; for Result&amp;lt;V, E&amp;gt; where V: FromIterator&amp;lt;A&amp;gt;&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://doc.rust-lang.org/std/option/enum.Option.html#impl-FromIterator%3COption%3CA%3E%3E-for-Option%3CV%3E"&gt;&lt;code&gt;FromIterator&amp;lt;Option&amp;lt;A&amp;gt;&amp;gt; for Option&amp;lt;V&amp;gt; where V: FromIterator&amp;lt;A&amp;gt;&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We know that that type &lt;code&gt;V&lt;/code&gt; can be our Vec or whatever, so these implementations provide what we need to get the whole magical &lt;code&gt;collect&lt;/code&gt; behavior to fall out.
The types are &lt;em&gt;scary&lt;/em&gt;, though, especially if you're not very familiar with strongly typed FP languages.&lt;/p&gt;
&lt;h1 id="how-do-you-find-this-out"&gt;How do you find this out?&lt;/h1&gt;
&lt;p&gt;Things like this are hard to discover on your own in Rust.
That's one of my laments with the language.&lt;/p&gt;
&lt;p&gt;How I discovered it: initally, I think I saw it in the book or when pairing with other people.
Later on, I also saw it in the &lt;code&gt;collect&lt;/code&gt; docs, which gave some very useful examples of how to use it for this use case.
It's also explained in &lt;a href="https://doc.rust-lang.org/rust-by-example/error/iter_result.html"&gt;Rust By Example&lt;/a&gt;&lt;sup class="footnote-reference"&gt;
  &lt;a href="https://ntietz.com/blog/rust-vec-of-result/#fn-3" id="fn-ref-3"&gt;3&lt;/a&gt;
&lt;/sup&gt;
, along with a few other examples.&lt;/p&gt;
&lt;p&gt;The type system here does get in the way of good discoverability, in my opinion, since it's not super clear what combinations of traits on which types will give you what you need.
I don't know how to improve it, other than talking gleefully about things that are fun like this and spreading the word.&lt;/p&gt;
&lt;p&gt;What other cool Rust things should the world know about?&lt;/p&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 23 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/rust-vec-of-result/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Note 119</title><link>https://qubyte.codes/notes/1697999909239</link><description>&lt;p&gt;I got to see Pip Blom’s first ever acoustic set at Resident Records in Brighton. It was amazing fun!&lt;/p&gt;

    &lt;img alt="Pip Blom and co playing a set. Two up front on acoustic guitars on a tiny temporary stage in front of the shop counter, and one on synths behind the counter. Both up front are singing." src="https://qubyte.codes/images/1697999718930.jpeg" /&gt;</description><author>Qubyte Codes</author><pubDate>Sun, 22 Oct 2023 21:38:29 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/notes/1697999909239</guid></item><item><title>Embed only the video from another post on X or Twitter</title><link>https://neosmart.net/blog/embed-only-the-video-from-another-post-on-x-or-twitter/</link><description>&lt;p&gt;Twitter has a new-ish feature that lets you embed only the video from another post or tweet in a post/tweet of your own (without quote-replying the source tweet itself). Only the video is then embedded in your post, and a &amp;#8230; &lt;a href="https://neosmart.net/blog/embed-only-the-video-from-another-post-on-x-or-twitter/"&gt;Continue reading &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
The post &lt;a href="https://neosmart.net/blog/embed-only-the-video-from-another-post-on-x-or-twitter/"&gt;Embed only the video from another post on X or Twitter&lt;/a&gt; first appeared on &lt;a href="https://neosmart.net/blog"&gt;The NeoSmart Files&lt;/a&gt;.</description><author>The NeoSmart Files</author><pubDate>Sun, 22 Oct 2023 20:33:56 GMT</pubDate><guid isPermaLink="true">https://neosmart.net/blog/embed-only-the-video-from-another-post-on-x-or-twitter/</guid></item><item><title>Finite jest</title><link>https://somethingaboutcats.substack.com/p/finite-jest</link><description>Recently I&amp;#8217;ve found myself quantifying my existence - or that which I&amp;#8217;ve got left - by &amp;#8220;expected number of good [X] remaining.&amp;#8221;</description><author>Something About Cats</author><pubDate>Sun, 22 Oct 2023 18:26:14 GMT</pubDate><guid isPermaLink="true">https://somethingaboutcats.substack.com/p/finite-jest</guid></item><item><title>Summary: Can't Hurt Me by David Goggins</title><link>https://www.chestergrant.com/summary-cant-hurt-me-by-david-goggins</link><description>&lt;div class="posthaven-post-body"&gt;&lt;p&gt;&lt;/p&gt;&lt;center&gt;        &lt;div class="posthaven-gallery" id="posthaven_gallery[2063633]"&gt;
                  &lt;p class="posthaven-file posthaven-file-image posthaven-file-state-processed"&gt;
          &lt;img class="posthaven-gallery-image" src="https://phaven-prod.s3.amazonaws.com/files/image_part/asset/3085197/OHpa1e2MYDZF4gm2fLwJyvZFIJY/medium_cover.png" /&gt;
        &lt;/p&gt;

        &lt;/div&gt;
&lt;/center&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;1. So I sought out pain, fell in love with suffering, and eventually transformed myself from the weakest piece of shit on the planet into the hardest man God ever created, or so I tell myself.&lt;/p&gt;&lt;p&gt;2. That night, after taking a shower, I wiped the steam away from our corroded bathroom mirror and took a good look. I didn’t like who I saw staring back. I was a low-budget thug with no purpose and no future. I felt so disgusted I wanted to punch that motherfucker in the face and shatter glass. Instead, I lectured him. It was time to get real.&lt;/p&gt;&lt;p&gt;3. “Look at you,” I said. “Why do you think the Air Force wants your punk ass? You stand for nothing. You are an embarrassment.”&lt;/p&gt;&lt;p&gt;4. “You are one dumb motherfucker. You read like a third grader. You’re a fucking joke! You’ve never tried hard at anything in your life besides basketball, and you have goals? That’s fucking hilarious.”&lt;/p&gt;&lt;p&gt;5. “You don’t see people in the military sagging their pants. You need to stop talking like a wanna-be-gangster. None of this shit is gonna cut it! No more taking the easy way out! It’s time to grow the fuck up!”&lt;/p&gt;&lt;p&gt;6. The Accountability Mirror kept me on point from then on, and though I was still young when this strategy came through me, since then I’ve found it useful for people at any stage in life.&lt;/p&gt;&lt;p&gt;7. The Accountability Mirror kept me on point from then on, and though I was still young when this strategy came through me, since then I’ve found it useful for people at any stage in life.&lt;/p&gt;&lt;p&gt;8. From then on, I brainwashed myself into craving discomfort. If it was raining, I would go run. Whenever it started snowing, my mind would say, Get your fucking running shoes on. Sometimes I wussed out and had to deal with it at the Accountability Mirror. But facing that mirror, facing myself, motivated me to fight through uncomfortable experiences, and, as a result, I became tougher. And being tough and resilient helped me meet my goals.&lt;/p&gt;&lt;p&gt;&lt;b&gt;9. That’s when I first realized that not all physical and mental limitations are real, and that I had a habit of giving up way too soon.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;10. Everything in life is a mind game! Whenever we get swept under by life’s dramas, large and small, we are forgetting that no matter how bad the pain gets, no matter how harrowing the torture, all bad things end.&lt;/p&gt;&lt;p&gt;11. Taking someone’s soul means you’ve gained a tactical advantage. Life is all about looking for tactical advantages, which is why we stole the Hell Week schedule, why we nipped Psycho’s heels on that run, and why I made a show of myself in the surf, humming the Platoon theme song. Each of those incidents was an act of defiance that empowered us.&lt;/p&gt;&lt;p&gt;12. When the water was chest high I began humming Adagio in Strings once more. Louder this time. Loud enough for that motherfucker to hear me over the crash of the surf. That song gave me life!&lt;/p&gt;&lt;p&gt;13. &lt;b&gt;Physical training is the perfect crucible to learn how to manage your thought process because when you’re working out, your focus is more likely to be single pointed, and your response to stress and pain is immediate and measurable. Do you hammer hard and snag that personal best like you said you would, or do you crumble? That decision rarely comes down to physical ability, it’s almost always a test of how well you are managing your own mind.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;14. &lt;b&gt;The reason it’s important to push hardest when you want to quit the most is because it helps you callous your mind.&lt;/b&gt; It’s the same reason why you have to do your best work when you are the least motivated. That’s why I loved PT in BUD/S and why I still love it today. Physical challenges strengthen my mind so I’m ready for whatever life throws at me, and it will do the same for you.&lt;/p&gt;&lt;p&gt;15. Failure is a part of life and now we all had to press on.&lt;/p&gt;&lt;p&gt;16. I felt it viscerally, and I used that concept to stuff a new kind of Cookie Jar. Inside it were all my past victories.&lt;/p&gt;&lt;p&gt;17. Like the time when I had to study three times as hard as anybody else during my senior year in high school just to graduate. That was a cookie. Or when I passed the ASVAB test as a senior and then again to get into BUD/S. Two more cookies. I remembered dropping over a hundred pounds in under three months, conquering my fear of water, graduating BUD/S at the top of my class, and being named Enlisted Honor Man in Army Ranger School (more on that soon). All those were cookies loaded with chocolate chunks.&lt;/p&gt;&lt;p&gt;18. I actually tapped into the emotional state I felt during those victories, and in so doing accessed my sympathetic nervous system once again. My adrenaline took over, the pain started to fade just enough, and my pace picked up. I began swinging my arms and lengthening my stride. My fractured feet were still a bloody mess, full of blisters, the toenails peeling off almost every toe, but I kept pounding, and soon it was me who was slaloming runners with pained expressions as I raced the clock.&lt;/p&gt;&lt;p&gt;19. The engine in a rocket ship does not fire without a small spark first. We all need small sparks, small accomplishments in our lives to fuel the big ones. Think of your small accomplishments as kindling. When you want a bonfire, you don’t start by lighting a big log. You collect some witch’s hair—a small pile of hay or some dry, dead grass. You light that, and then add small sticks and bigger sticks before you feed your tree stump into the blaze. Because it’s the small sparks, which start small fires, that eventually build enough heat to burn the whole fucking forest down.&lt;/p&gt;&lt;p&gt;20. I’m talking about utilizing past successes to fuel you to new and bigger ones. Because in the heat of battle, when shit gets real, we need to draw inspiration to push through our own exhaustion, depression, pain, and misery. We need to spark a bunch of small fires to become the motherfucking inferno.&lt;/p&gt;&lt;p&gt;21. By now, I’m sure you can tell that it doesn’t take much for me to become obsessed. Some criticize my level of passion, but I’m not down with the prevailing mentalities that tend to dominate American society these days; the ones that tell us to go with the flow or invite us to learn how to get more with less effort. &lt;b&gt;Fuck that shortcut bullshit.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;22. &lt;b&gt;Sadly, most of us give up when we’ve only given around 40 percent of our maximum effort. Even when we feel like we’ve reached our absolute limit, we still have 60 percent more to give!&lt;/b&gt;&lt;/p&gt;&lt;p&gt;23. &lt;b&gt;But nobody taps their reserve 60 percent right away or all at once. The first step is to remember that your initial blast of pain and fatigue is your governor talking. Once you do that, you are in control of the dialogue in your mind, and you can remind yourself that you are not as drained as you think. That you haven’t given it your all. Not even close. Buying into that will keep you in the fight, and that’s worth an extra 5 percent. Of course, that’s easier read than done.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;24. Most of us are motivated as hell to do anything to pursue our dreams until those around us remind us of the danger, the downside, our own limitations, and all the people before us that didn’t make it. Sometimes the advice comes from a well-intentioned place. They really believe they are doing it for our own good but if you let them, these same people will talk you out of your dreams, and your governor will help them do it.&lt;/p&gt;&lt;p&gt;25. That’s one reason I invented the Cookie Jar. We must create a system that constantly reminds us who the fuck we are when we are at our best, because life is not going to pick us up when we fall.&lt;/p&gt;&lt;p&gt;26. &lt;b&gt;From this point forward, accept the following as Goggins’s laws of nature: &lt;/b&gt;&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;b&gt;You will be made fun of. &lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;You will feel insecure. &lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;You may not be the best all the time. &lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;You may be the only black, white, Asian, Latino, female, male [fill in your identity here] in a given situation. &lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;There will be times when you feel alone. &lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;b&gt;Get over it!&lt;/b&gt;&lt;/p&gt;&lt;p&gt;27. In the military we always say we don’t rise to the level of our expectations, we fall to the level of our training.&lt;/p&gt;&lt;p&gt;28. First, a quick reminder of how this process works. In 1999, when I weighed 297 pounds, my first run was a quarter mile. Fast forward to 2007, I ran 205 miles in thirty-nine hours, nonstop. I didn’t get there overnight, and I don’t expect you to either. Your job is to push past your normal stopping point.&lt;/p&gt;&lt;p&gt;&lt;b&gt;29. Whether you are running on a treadmill or doing a set of push-ups, get to the point where you are so tired and in pain that your mind is begging you to stop. Then push just 5 to 10 percent further. If the most push-ups you have ever done is one hundred in a workout, do 105 or 110. If you normally run thirty miles each week, run 10 percent more next week.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;30. This gradual ramp-up will help prevent injury and allow your body and mind to slowly adapt to your new workload. It also resets your baseline, which is important because you’re about to increase your workload another 5 to 10 percent the following week, and the week after that.&lt;/p&gt;&lt;p&gt;31. If you want to master the mind and remove your governor, you’ll have to become addicted to hard work. Because passion and obsession, even talent, are only useful tools if you have the work ethic to back them up.&lt;/p&gt;&lt;p&gt;32. For years I’ve lived like a monk. I don’t see or spend time with a lot of people. My circle is very tight. I post on social media once or twice a week and I never check anybody else’s feeds because I don’t follow anyone. That’s just me. I’m not saying you need to be that unforgiving, because you and I probably don’t share the same goals. But I know you have goals too, and room for improvement, or you wouldn’t be reading my book, and I guarantee that if you audited your schedule you’d find time for more work and less bullshit.&lt;/p&gt;&lt;p&gt;&lt;b&gt;33. A lot of us surround ourselves with people who speak to our desire for comfort. People who would rather treat the pain of our wounds and prevent further injury than help us callous over them and try again. We need to surround ourselves with people who will tell us what we need to hear, not what we want to hear, but at the same time not make us feel we’re up against the impossible. My mother was my biggest fan. Whenever I failed in life she was always asking me when and where I would go after it again. She never said, Well, maybe it isn’t meant to be.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;34. In life, there is no gift as overlooked or inevitable as failure. I’ve had quite a few and have learned to relish them, because if you do the forensics you’ll find clues about where to make adjustments and how to eventually accomplish your task.&lt;/p&gt;&lt;p&gt;35. If I was going to be the next athlete to smash popular perception, I’d need to stop listening to doubt, whether it streamed in from the outside or bubbled up from within, and the best way to do that was to decide that the pull-up record was already mine.&lt;/p&gt;&lt;/div&gt;</description><author>Chester Grant</author><pubDate>Sun, 22 Oct 2023 17:21:20 GMT</pubDate><guid isPermaLink="true">https://www.chestergrant.com/summary-cant-hurt-me-by-david-goggins</guid></item><item><title>Read-once Objects</title><link>https://allan.reyes.sh/posts/read-once-objects/</link><description>&lt;p&gt;Hello! In the previous post, I covered &lt;a href="https://allan.reyes.sh/posts/tainted-types/"&gt;tainted types&lt;/a&gt; as the second of a &lt;a href="https://allan.reyes.sh/tags/domain-primitives"&gt;domain primitives
series&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Tainted types helped us package dangerous user input into a value object to
ensure that all the areas where it propagated to are handled correctly. We&amp;rsquo;ll
extend the value object concept to create a container for sensitive data. It&amp;rsquo;s
one of my &lt;em&gt;favorite&lt;/em&gt; domain primitives. They are &lt;em&gt;fantastic&lt;/em&gt; for storing and
tracking sensitive data, and preventing them from &lt;a href="https://www.bleepingcomputer.com/news/security/twitter-admits-recording-plaintext-passwords-in-internal-logs-just-like-github/"&gt;leaking into logs&lt;/a&gt;.
(&lt;em&gt;I&amp;rsquo;m sure you&amp;rsquo;ve been there.&lt;/em&gt;)&lt;/p&gt;</description><author>allan.reyes.sh</author><pubDate>Sun, 22 Oct 2023 17:00:00 GMT</pubDate><guid isPermaLink="true">https://allan.reyes.sh/posts/read-once-objects/</guid></item><item><title>Tainted Types</title><link>https://allan.reyes.sh/posts/tainted-types/</link><description>&lt;p&gt;Welcome! In the previous post, I covered &lt;a href="https://allan.reyes.sh/posts/branded-types/"&gt;branded types&lt;/a&gt; as the first of a &lt;a href="https://allan.reyes.sh/tags/domain-primitives"&gt;domain primitives
series&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Where branded types helped us ensure upstream code is correct, we can flip the
script and use another type of domain object (specifically, a &lt;em&gt;value object&lt;/em&gt;) to
ensure that downstream code is correct. As the adage goes, &amp;ldquo;always validate user
input!&amp;rdquo; We can &amp;ldquo;taint&amp;rdquo; user input as it enters our system and force everything
that handles it to either validate or sanitize it.&lt;/p&gt;</description><author>allan.reyes.sh</author><pubDate>Sun, 22 Oct 2023 16:00:00 GMT</pubDate><guid isPermaLink="true">https://allan.reyes.sh/posts/tainted-types/</guid></item><item><title>DNS over HTTPS on macOS</title><link>https://swag.industries/dns-over-https-natively-on-macos-and-ios/</link><description>Improve your privacy by using DNS over HTTPS. Look up hostnames privately and avoid giving too much info to your ISP. This is a tutorial for dnscrypt proxy on macOS.</description><author>Swag Industries</author><pubDate>Sun, 22 Oct 2023 12:29:15 GMT</pubDate><guid isPermaLink="true">https://swag.industries/dns-over-https-natively-on-macos-and-ios/</guid></item><item><title>Semantic Search Tutorial</title><link>https://geo.rocks/post/semantic-search-tutorial/</link><description>&lt;p&gt;&lt;img alt="Copernicus Services Semantic Search App Interface in Dark Mode" src="https://geo.rocks/img/copernicus-services-semantic-search-interface-dark.png" title="Using custom SVG-Markers in QGIS" /&gt;&lt;/p&gt;
&lt;p&gt;A full semantic search tutorial about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;data mining with requests and beautifulsoup&lt;/li&gt;
&lt;li&gt;preprocessing in pandas&lt;/li&gt;
&lt;li&gt;chunking the document text in smaller paragraphs of the right size for the ML model&lt;/li&gt;
&lt;li&gt;creating embeddings for each chunk&lt;/li&gt;
&lt;li&gt;calculating the mean embedding for each document&lt;/li&gt;
&lt;li&gt;saving data as gzipped json (small file size &amp;amp; easy and fast to read in js with pako.js)&lt;/li&gt;
&lt;li&gt;creating a static web app based on &lt;a href="https://github.com/xenova/transformers.js"&gt;transformers.js&lt;/a&gt; on GitHub Pages&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://do-me.github.io/copernicus-services-semantic-search/"&gt;App here&lt;/a&gt;&lt;/p&gt;</description><author>Geography &amp;amp; Coding</author><pubDate>Sun, 22 Oct 2023 12:18:04 GMT</pubDate><guid isPermaLink="true">https://geo.rocks/post/semantic-search-tutorial/</guid></item><item><title>The use and abuse of the dev branch pattern</title><link>https://www.qword.net/2023/10/22/the-use-and-abuse-of-the-dev-branch.html</link><description>Have you ever wanted to make local changes to a repository for your own benefit, but don’t want to send them remotely? Maybe you’ve once wanted to:</description><author>QWORD</author><pubDate>Sun, 22 Oct 2023 07:24:53 GMT</pubDate><guid isPermaLink="true">https://www.qword.net/2023/10/22/the-use-and-abuse-of-the-dev-branch.html</guid></item><item><title>Search / Filter posts by flair on Reddit</title><link>https://web.navan.dev/posts/2023-10-22-search-by-flair-reddit.html</link><description>Search posts by flair on Reddit Web by using _</description><author>Navan's Archive</author><pubDate>Sun, 22 Oct 2023 03:37:00 GMT</pubDate><guid isPermaLink="true">https://web.navan.dev/posts/2023-10-22-search-by-flair-reddit.html</guid></item><item><title>Using Tailscale Custom OIDC with Authelia and Carpal</title><link>https://blog.janissary.xyz/posts/tailscale-oidc-authelia-carpal</link><author>janissary</author><pubDate>Sun, 22 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.janissary.xyz/posts/tailscale-oidc-authelia-carpal</guid></item><item><title>Four layers of self-improvement</title><link>https://jodavaho.io/posts/four-layers-self-improvement.html</link><description>&lt;p&gt;I&amp;rsquo;ve been tracking my OKR&amp;rsquo;s since 2014. Sometimes I accomplish what I set out
for, sometimes I don&amp;rsquo;t. Sometimes I accomplish &lt;em&gt;what I put in OKRs&lt;/em&gt; but didn&amp;rsquo;t
accomplish what I wanted.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what I&amp;rsquo;ve learned.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There are usually four types of OKRs, and not differentiating types makes it &lt;em&gt;really hard&lt;/em&gt; to get what you wanted&lt;/li&gt;
&lt;li&gt;Not only are there four, but they are roughly speaking prerequisites of each other.&lt;/li&gt;
&lt;li&gt;Not only that, but looking at all the self-help books I&amp;rsquo;ve read, it actually seems like most the books &lt;em&gt;also&lt;/em&gt; fit into these four categories, and just reading them out of order makes no sense.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These categories are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Intent-building (actions that create action). Perhaps these are better known as &amp;ldquo;values&amp;rdquo;. When lost, you feel listless.&lt;/li&gt;
&lt;li&gt;Foundational stuff (actions that everyone needs to do to be &amp;ldquo;better&amp;rdquo;). Basic self care is here. When lost, you feel unhealthy and &amp;ldquo;stuck&amp;rdquo; even if you have good intent.&lt;/li&gt;
&lt;li&gt;Focus activities (the stuff you need to realize your specific vision of self). Family, job duties, social activities, etc are here. These &amp;ldquo;round out&amp;rdquo; an otherwise healthy person.&lt;/li&gt;
&lt;li&gt;Specialized activities (the stuff that really defines who you are). These are the things you are proud of. Resume-level or hobby-level things like &amp;ldquo;I love boxing&amp;rdquo; or &amp;ldquo;I love making cabinets&amp;rdquo;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So my strategy going forward is to work from the bottom up. If I find a day where I can&amp;rsquo;t get anything done, I&amp;rsquo;ll at least work on intent. Maybe it seems obvious, but just focusing on what I want and planning a better tomorrow makes tomorrow much easier. And focusing on intent allows you to remember throughout the day to work on your goals. Lately, just taking a break from work during the day to move an inch forward is a huge help. For example 10 pullups between meetings.&lt;/p&gt;
&lt;p&gt;Anyway, I will shortly analyze 3rd quarter, but for now, this idea of layering on improvements is something I&amp;rsquo;m noodling as I redefine what success looks like.&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Sun, 22 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/four-layers-self-improvement.html</guid></item><item><title>Ping</title><link>http://localhost:4000/2023/10/22/ping.html</link><description>Ping</description><author>ms75</author><pubDate>Sun, 22 Oct 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">http://localhost:4000/2023/10/22/ping.html</guid></item><item><title>Branded Types</title><link>https://allan.reyes.sh/posts/branded-types/</link><description>&lt;blockquote&gt;
&lt;p&gt;Code that handles bare string primitives with important semantic and
security concerns isn&amp;rsquo;t using the type system to its fullest extent.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;—Brendan Eich, probably&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is the first post of a &lt;a href="https://allan.reyes.sh/tags/domain-primitives"&gt;series on domain
primitives&lt;/a&gt; that show how they lay a secure foundation
for software. I&amp;rsquo;ll discuss alternatives to string primitives using an
&lt;a href="https://expressjs.com/"&gt;Express&lt;/a&gt; web server written in &lt;a href="https://www.typescriptlang.org/"&gt;TypeScript&lt;/a&gt; as an example,
but the concepts introduced here can be generalized to replace &lt;em&gt;any&lt;/em&gt; primitive
and they can be used in other languages that support type guards.&lt;/p&gt;</description><author>allan.reyes.sh</author><pubDate>Sat, 21 Oct 2023 16:00:00 GMT</pubDate><guid isPermaLink="true">https://allan.reyes.sh/posts/branded-types/</guid></item><item><title>Automating myself out of a job?</title><link>https://blog.separateconcerns.com/2023-10-21-automating-job.html</link><description>&lt;p&gt;Since I’m &lt;a href="https://blog.separateconcerns.com/2023-03-31-joining-finegrain.html"&gt;back in AI&lt;/a&gt;, a recurring question has been whether we are collectively automating ourselves — software engineers and other kinds of tech people — out of our jobs. So, are we? And if so, how long will it take until that becomes a problem, and is this ultimately bad? I don’t have all the answers, but here is some insight.&lt;/p&gt;
&lt;p&gt;First, we have always tried to automate repetitive things. This is why we write libraries and frameworks to move up the ladder of abstraction — which by the way comes with its own set of challenges. We are reasonably good at it, and that is part of what justifies our high salaries, but it is clear that &lt;a href="https://www.youtube.com/watch?v=l2btv0yUPNQ"&gt;very small shell scripts&lt;/a&gt; will not be enough to replace us entirely.&lt;/p&gt;
&lt;p&gt;In the last few years, large language models have shown somewhat impressive ability to generate code, to the point that AI replacing human programmers in the next decade has become a possibility. Like many, I have been experimenting with those tools, and my impressions are mixed.&lt;/p&gt;
&lt;p&gt;On the one hand, I am convinced by tools that are integrated into editors and work like a better autocomplete. I have been using GitHub Copilot for a few months and it is helpful. It does not &lt;strong&gt;always&lt;/strong&gt; suggest the right thing, but it does so often enough that it saves me time.&lt;/p&gt;
&lt;p&gt;On the other hand, I am skeptical of the approach that consists of generating larger chunks of code using tools such as ChatGPT. It may work for one-shot throwaway scripts, but it is rarely helpful for writing “real” software. The reason is that it fails in two ways. The first one is generating completely wrong code (too simplistic, solving the wrong problem…), and the second one is making less obvious mistakes in the middle of code that otherwise looks good. While the former is not such a problem, the latter definitely is.&lt;/p&gt;
&lt;p&gt;An analogy you could make is that they both work like junior programmers making mistakes, but Copilot pairs with you whereas the larger code chunks generation approach is similar to the pull request / code review workflow. If you have high-quality standards, reviewing and fixing bad code is often more time-consuming than writing it from scratch. With junior developers you do it anyway because you invest in their growth, hoping eventually they will write good code that saves you time. With current AI that learning doesn’t happen, or at least not fast enough.&lt;/p&gt;
&lt;p&gt;Also, there is clearly a difference depending on the kind of code you write and how innovative it is. For instance, for my job at Finegrain, Copilot is very helpful with Web backend code (using &lt;a href="https://github.com/pallets/quart"&gt;Quart&lt;/a&gt;, SQL requests…), but much less so when I work on deep learning models using &lt;a href="https://github.com/finegrain-ai/refiners"&gt;our Open Source framework Refiners&lt;/a&gt;. This reinforces my view that programming bootcamps that teach superficial concepts to put people on the market as early as possible are a trap; if you are a programmer and worry about your job safety, I suggest you invest in fundamentals.&lt;/p&gt;
&lt;p&gt;If that wasn’t reason enough, AI appears to be better at solving high-level problems. By that I mean that it is good at answering questions that an average non-technical person could ask, such as “create a nature-themed landing page for my new business which sells coconut oil shampoo”, but less good at understanding the kind of problems developers can only talk about with other developers.&lt;/p&gt;
&lt;p&gt;So it is clear to me that I will not be replaced by AI next year, but what about in 10 years? What about in 20? That is where knowing a bit about how AI works helps.&lt;/p&gt;
&lt;p&gt;Let’s start by saying I was not surprised that code generation turned out to be an area where deep learning shines. I have been saying for over a decade that &lt;a href="https://marshallbrain.com/manna1"&gt;this revolution will affect white-collar jobs before blue-collar&lt;/a&gt;. Moreover, the current wave of AI is good at replicating things for which it has tons of examples, and we gave it that with Open Source code. This is also why AI is good at generating pictures, stories, or web pages: it is all out there on the Internet. Humans are still better at learning from fewer examples, even though there has been a lot of progress in that area recently with foundational models and few-shot learning.&lt;/p&gt;
&lt;p&gt;Now, my belief is that although recent advances are a good predictor for upcoming short-term improvements, it is not for the long term (by that I mean a few years away). I am 100% sure that we will get better copilots and image models in 2024, but I also think it is extremely hard to predict what will happen in 2030. The reason for this is that large foundational models exhibit a property called &lt;a href="https://www.quantamagazine.org/the-unpredictable-abilities-emerging-from-large-ai-models-20230316/"&gt;emergence&lt;/a&gt;, which you can basically interpret as: “we know increasing the resources available to the model will make it better, but we do not know exactly at what”. And let us be clear here: that does not mean we do not know how to improve a model along a specific dimension, how to teach it to be better at something specific. This is precisely what I am working on! This means I believe the major advances in AI have not and will not come from such deliberate approaches, but from just throwing more hardware, more / better data, and marginally better core architectures at models, and seeing what they learn to do.&lt;/p&gt;
&lt;p&gt;In other words, I do think AI may get better at the things it doesn’t do well enough to replace programmers, but I would say it is about as likely to happen to other “intellectual” professions such as managers, lawyers, doctors, architects, and so on.&lt;/p&gt;
&lt;p&gt;One point that I’d like to emphasize though: a few years ago people who don’t work in AI used to think that “creativity” was a human prerogative and that AI would never challenge “artistic” professions. I hope by now you have figured out this was wrong. I’ll go even further: one of the main problems in AI today is that it tends to be &lt;strong&gt;too&lt;/strong&gt; creative. We keep trying to figure out ways to curb that tendency to be like a child who tells you about their day at school but invents half of it. My personal view on it is that this is not something we learned about AI, this is something we confirmed about human nature: that evolution made us powerful pattern-matching machines, and that creativity is just an emergent property that came out of it. And I say confirmed because personally I have always thought it to be the case: there were hints at it even before AI, from observing how children learn to our unconscious biases.&lt;/p&gt;
&lt;p&gt;That digression aside, let us now assume AI &lt;strong&gt;does&lt;/strong&gt; effectively replace programmers. What then? Does that depress me? What am I to do with my life?&lt;/p&gt;
&lt;p&gt;Well, the truth is I am not concerned. Programming is an essential part of me but I had been writing code for over a decade before I started doing it professionally, and even nowadays I still write some code for fun with no financial reward at all. I’ll keep doing that even if AI beats us at it. Did people stop playing chess or any of the many games AI has beaten us at for years? Did we stop making tools and furniture by hand now that most of the industry is automated? Of course not. So I’ll probably turn to a form of craftsmanship I already enjoy practicing, or even to more &lt;a href="https://www.camilleroux.com/2023/07/26/art-generatif-de-la-decouverte-a-la-publication-dun-projet-dans-un-studio-prestigieux/"&gt;artistic forms of code&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Will I still make a living out of it? Probably not. But I am confident I will adapt. Even if the entire tech industry is automated I know that I can learn other unrelated marketable skills in a matter of months if not weeks — because I have already done it. And I am in a better position than most to notice what jobs are under threat.&lt;/p&gt;
&lt;p&gt;Extrapolating to the entire society, will it not result in a major problem if too many jobs are automated? Well yes, it will, and I do believe at some point we will need to tackle that issue. Note that the issue is &lt;strong&gt;not&lt;/strong&gt; that there won’t be enough jobs for everyone. Jobs are not something we fundamentally need, if anything most of us work too much today. And it is not that there won’t be enough value creation for everyone either: those machines &lt;strong&gt;will&lt;/strong&gt; create value. The problem is one of allocation.&lt;/p&gt;
&lt;p&gt;Here is how I explain my view on it: I actually like capitalism, and I think it is in part responsible for a lot of the progress — including &lt;strong&gt;social&lt;/strong&gt; progress — that has happened in the world in the last few centuries. However, capitalism was designed on the assumption that the majority of the value-add comes from human work, and a minority from capital. Automation will reverse that, and in a system where capital is responsible for a large majority of the value-add capitalism breaks down.&lt;/p&gt;
&lt;p&gt;So how do we prepare for this? Well to start let me recommend &lt;a href="https://marshallbrain.com/manna1"&gt;a short novel I already linked earlier&lt;/a&gt;. But I think the most important thing to understand is that we are not there yet, and the worst thing to do would be to try to implement parts of the endgame now. See, this is not a zero-sum game, but the world is not a bunch of altruistic parties either. So if you start — for instance — taxing machines now, or worse trying to slow technical progress, rest assured some of your neighbors won’t, and the end result is &lt;strong&gt;they&lt;/strong&gt; will own the machines and not you. So the only reasonable thing to do if you are a benevolent but realistic government is accelerate as fast as you can, capture a large enough chunk of that capital, and &lt;strong&gt;then&lt;/strong&gt; once things settle down make sure it benefits your population. All that while avoiding exploding under the pressure of the inevitable social unrest in the meantime.&lt;/p&gt;
&lt;p&gt;Democratic governments of the world, I wish you good luck and hope you will succeed.&lt;/p&gt;</description><author>Separate Concerns</author><pubDate>Sat, 21 Oct 2023 15:20:00 GMT</pubDate><guid isPermaLink="true">https://blog.separateconcerns.com/2023-10-21-automating-job.html</guid></item><item><title>2023-10-21-001</title><link>https://srijan.ch/notes/2023-10-21-001</link><description>I've been working on an Indieweb-enabled site redesign using Kirby CMS + TailwindCSS.</description><author>Srijan Choudhary, all posts</author><pubDate>Sat, 21 Oct 2023 10:45:00 GMT</pubDate><guid isPermaLink="true">https://srijan.ch/notes/2023-10-21-001</guid></item><item><title>Fediverse defederation considerations</title><link>https://seirdy.one/notes/2023/10/20/fediverse-defederation-considerations/</link><description>&lt;aside&gt;&lt;p&gt;
	Reply to &lt;span class="h-cite u-in-reply-to"&gt;&lt;cite class="p-name"&gt;&lt;a class="u-url" href="https://hachyderm.io/@hrefna/111269499783284609"&gt;what are the things you would like to know before applying a domain block?&lt;/a&gt;&lt;/cite&gt; by
		&lt;span class="h-card vcard p-author"&gt;&lt;a class="u-url url" href="https://hachyderm.io/@hrefna"&gt;&lt;span class="p-name fn n"&gt;Hrefna&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;
	&lt;/span&gt;
&lt;/p&gt;&lt;/aside&gt;&lt;p&gt;What I consider:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;Is an offending post a property of the instance or the user? It may reflect the instance if it’s written or reblogged by a staff member or part of a large pattern.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;If it’s a property of the user but reporting doesn’t work, do I have the spoons to moderate their instance on a user-by-case basis? I usually do this only if there are existing connections or if the instance is run by and for marginalized groups.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;If I see slur usage: is there plausible deniability for the slurs to be used in a reclaimed context, or for the users in question to not be native speakers? Glossing over this once earned &lt;a href="https://seirdy.one/posts/2023/05/02/fediverse-blocklists/#july-2023-bad-receipt-for-bungleonline"&gt;a subsection in my blocklists’ “Mistakes Made” section&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;If there’s harassment, I might want to check in with the victim to see if it’s okay to save a receipt featuring them and if I should make a callout.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Do staff members run other instances? I could treat all their instances collectively.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Minor factors that &lt;em&gt;do not&lt;/em&gt; directly inform the decision to defederate, but &lt;em&gt;do&lt;/em&gt; inform the decision to look deeper before dismissing the instance:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Is the instance staff close to sufficiently bad actors? If so, the staff members almost certainly post similar content. It’s worth the extra time to find the receipts.&lt;/li&gt;&lt;li&gt;If the instance is brand-new, I might re-visit it in a week or two to see if it cleaned up.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Steps I perform:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Save receipts with multiple archiving services. Sometimes one service doesn’t render an archive properly, so it’s good to have alternatives.&lt;/li&gt;&lt;li&gt;Describe reasons with those links, and make it detailed enough for readers to understand the reason &lt;em&gt;without&lt;/em&gt; visiting any  links. Archived links are for proof more than understanding.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;I don’t consider whether the domain is still alive, assuming I have receipts saved from when it was online. Dead instances do sometimes come back, and blocking dead instances is harmless.&lt;/p&gt;</description><author>All content on Seirdy’s Home</author><pubDate>Sat, 21 Oct 2023 04:41:13 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/notes/2023/10/20/fediverse-defederation-considerations/</guid></item><item><title>Surfaces with pentominoes</title><link>https://river.me/blog/surfaces-with-pentominoes/</link><description>Embedding a grid on a surface doesn&amp;rsquo;t really work, but we can approximate.</description><author>River Writes - A MediaWiki Blog</author><pubDate>Sat, 21 Oct 2023 04:18:07 GMT</pubDate><guid isPermaLink="true">https://river.me/blog/surfaces-with-pentominoes/</guid></item><item><title>The Devil and the Cerulean Sweater</title><link>https://maximumeffort.substack.com/p/the-devil-and-the-cerulean-sweater</link><description>The Devil Wears Prada, Anne Hathaway's cerulean sweater, Rayleigh scattering, and and extraterrestrial solar spectra. Who is right, Dylan or Meryl Streep?</description><author>Maximum Effort, Minimum Reward</author><pubDate>Sat, 21 Oct 2023 03:02:57 GMT</pubDate><guid isPermaLink="true">https://maximumeffort.substack.com/p/the-devil-and-the-cerulean-sweater</guid></item><item><title>How to Dockerize a Prisma Nest.js Application</title><link>https://www.tderflinger.com/en/dockerize-prisma-nestjs</link><description>Often people use Nest.js and Prisma together when they program a backend application. Then there is the question of how to deploy the application with Docker. This article shows you how.</description><author>Thoughts by Thomas Derflinger</author><pubDate>Sat, 21 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.tderflinger.com/en/dockerize-prisma-nestjs</guid></item><item><title>Accelerating JavaScript arrays by 10x for Vector Search 🏹</title><link>https://ashvardanian.com/posts/javascript-ai-vector-search/</link><description>Supercharging JavaScript's TypedArray with C bindings and SIMD achieves 10x speedup over native Arrays for AI vector operations in Node.js.</description><author>Ash's Blog</author><pubDate>Sat, 21 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ashvardanian.com/posts/javascript-ai-vector-search/</guid></item><item><title>How to run multiple versions of React side-by-side using Single Spa</title><link>https://tiberriver256.github.io/web%20development/how-to-run-multiple-react-versions-using-single-spa/</link><description>&lt;p&gt;This seems like it should be easy right?
&lt;a href="https://single-spa.js.org/"&gt;Single-spa&lt;/a&gt; is a micro-frontend framework designed
to handle running multiple SPA frameworks side-by-side. It’s pretty
straightforward in all the other frameworks… but not so in React. Let’s dig
into why that is.&lt;/p&gt;

&lt;h2 id="whats-the-problem"&gt;What’s the problem?&lt;/h2&gt;

&lt;p&gt;In order to use React you always have to install two packages:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;react&lt;/code&gt; - The main runtime for React&lt;/li&gt;
  &lt;li&gt;Some other package that handles the rendering (i.e.,
&lt;code class="language-plaintext highlighter-rouge"&gt;react-dom&lt;/code&gt;/&lt;code class="language-plaintext highlighter-rouge"&gt;react-canvas&lt;/code&gt;/&lt;code class="language-plaintext highlighter-rouge"&gt;react-native&lt;/code&gt;/&lt;code class="language-plaintext highlighter-rouge"&gt;react-pdf&lt;/code&gt;/etc.)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This allows developers to plug multiple renderers into the React runtime and
magically grants everyone who knows React the ability to write JSX code that
works in a bunch of random places.&lt;/p&gt;

&lt;p&gt;Sounds magical! Why is this a problem again?&lt;/p&gt;

&lt;p&gt;The problem only comes when we’re trying to work with single-spa. Let’s setup a
quick project to show how this works:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;I create a root config&lt;/li&gt;
  &lt;li&gt;I create a React project running React 17, setting up my webpack externals to
look like: &lt;code class="language-plaintext highlighter-rouge"&gt;{ react: 'react-17', ['react-dom']: 'react-dom-17' }&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;I create another React project running React 18, setting up my webpack
externals to look like &lt;code class="language-plaintext highlighter-rouge"&gt;{ react: 'react-18', ['react-dom']: 'react-dom-18 }&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;I add import maps for &lt;code class="language-plaintext highlighter-rouge"&gt;react-17&lt;/code&gt;, &lt;code class="language-plaintext highlighter-rouge"&gt;react-dom-17&lt;/code&gt;, &lt;code class="language-plaintext highlighter-rouge"&gt;react-18&lt;/code&gt;, &lt;code class="language-plaintext highlighter-rouge"&gt;react-dom-18&lt;/code&gt;
all pointing to their various &lt;code class="language-plaintext highlighter-rouge"&gt;https://cdn.jsdelivr.net/npm/&lt;/code&gt; urls.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We boot up the site and we get a nice fat error message:
&lt;code class="language-plaintext highlighter-rouge"&gt;Unable to resolve bare specifier 'react' from https://cdn.jsdelivr.net/npm/react-dom@17.0.2/umd/react-dom.development.js&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The reason we get this error message is that &lt;code class="language-plaintext highlighter-rouge"&gt;react-dom&lt;/code&gt; was bundled with a
dependency on &lt;code class="language-plaintext highlighter-rouge"&gt;react&lt;/code&gt;. So, even though we have our application code swapping out
every &lt;code class="language-plaintext highlighter-rouge"&gt;react&lt;/code&gt; import with either a &lt;code class="language-plaintext highlighter-rouge"&gt;react-17&lt;/code&gt; or a &lt;code class="language-plaintext highlighter-rouge"&gt;react-18&lt;/code&gt; import and we’re
doing the same for where our application code references &lt;code class="language-plaintext highlighter-rouge"&gt;react-dom&lt;/code&gt; we can’t do
anything about the code inside the &lt;code class="language-plaintext highlighter-rouge"&gt;react-dom&lt;/code&gt; library that is looking for a
module named &lt;code class="language-plaintext highlighter-rouge"&gt;react-dom&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It’s a problem unique to React because of it’s two package delivery system. If
they shipped it as one package like Angular/Vue/etc. then we wouldn’t be having
this problem.&lt;/p&gt;

&lt;h2 id="the-solution"&gt;The solution&lt;/h2&gt;

&lt;p&gt;We’re going to re-bundle up our own distribution of the &lt;code class="language-plaintext highlighter-rouge"&gt;react-dom&lt;/code&gt; libraries.
Running it through webpack first. That way we can tell &lt;code class="language-plaintext highlighter-rouge"&gt;react-dom-17&lt;/code&gt; to import
&lt;code class="language-plaintext highlighter-rouge"&gt;react-17&lt;/code&gt; and we can tell &lt;code class="language-plaintext highlighter-rouge"&gt;react-dom-18&lt;/code&gt; to import &lt;code class="language-plaintext highlighter-rouge"&gt;react-18&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What’s the easiest way to do that?&lt;/p&gt;

&lt;p&gt;Let’s use &lt;code class="language-plaintext highlighter-rouge"&gt;npx create-single-spa&lt;/code&gt; to spin out two &lt;code class="language-plaintext highlighter-rouge"&gt;utility modules&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;One to re-export &lt;code class="language-plaintext highlighter-rouge"&gt;react-dom@17&lt;/code&gt; and have it’s webpack externals set to:
&lt;code class="language-plaintext highlighter-rouge"&gt;{ react: 'react-17' }&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;One to re-export &lt;code class="language-plaintext highlighter-rouge"&gt;react-dom@18&lt;/code&gt; and have it’s webpack externals set to:
&lt;code class="language-plaintext highlighter-rouge"&gt;{ react: 'react-18' }&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The only file in both projects will look like this:&lt;/p&gt;

&lt;div class="language-js highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ReactDOM&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-dom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;ReactDOM&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-dom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now in our import maps we reference our own utility module versions of react-dom
instead of the ones hosted on cdn.jsdelivr.net and things just work!&lt;/p&gt;

&lt;p&gt;&lt;img alt="" src="https://res.cloudinary.com/dhff6zdnc/image/fetch/c_limit,w_800,q_auto,f_auto/https://tiberriver256.github.io/images/how-to-run-multiple-react-versions-using-single-spa/multiple-react-versions-yay.png" /&gt;&lt;/p&gt;

&lt;p&gt;If you want to check out the working code,
&lt;a href="https://github.com/Tiberriver256/multiple-react-versions-single-spa"&gt;I’ve got a sample repository on Github&lt;/a&gt;.&lt;/p&gt;

    &lt;p&gt;&lt;a href="https://tiberriver256.github.io/web%20development/how-to-run-multiple-react-versions-using-single-spa/"&gt;How to run multiple versions of React side-by-side using Single Spa&lt;/a&gt; was originally published by Micah Rairdon at &lt;a href="https://tiberriver256.github.io"&gt;Tiberriver256&lt;/a&gt; on October 21, 2023.&lt;/p&gt;</description><author>Tiberriver256</author><pubDate>Sat, 21 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://tiberriver256.github.io/web%20development/how-to-run-multiple-react-versions-using-single-spa/</guid></item><item><title>Linux Distro Hopping is a Bad Idea</title><link>https://jasoneckert.github.io/myblog/distro-hopping/</link><description>&lt;p&gt;&lt;img alt="Linux distros" src="distros.png#center" title="Linux distros" /&gt;&lt;/p&gt;
&lt;h2 id="what-is-distro-hopping"&gt;What is distro hopping?&lt;/h2&gt;
&lt;p&gt;In the &lt;a href="https://en.wikipedia.org/wiki/Linux"&gt;Linux&lt;/a&gt; community, it&amp;rsquo;s not uncommon to install and use several different Linux distributions (or distros) over time - a process called &lt;em&gt;&lt;strong&gt;distro hopping&lt;/strong&gt;&lt;/em&gt;. After all, there are hundreds of different distros available - and because each one is often geared to provide the optimal experience for a specific user or use-case (e.g., gaming), it&amp;rsquo;s become commonplace to assume that there must be a specific Linux distro out there that will suit your individual needs. Consequently, most Linux users believe that you should choose a Linux distro using the same process as choosing a wardrobe or eyewear frames. And that means trying on many different Linux distros to see how they look ;-)&lt;/p&gt;</description><author>Jason Eckert's Website and Blog</author><pubDate>Sat, 21 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jasoneckert.github.io/myblog/distro-hopping/</guid></item><item><title>[Computing] Mitigating the Hetzner/Linode XMPP.ru MitM interception incident</title><link>https://www.devever.net/~hl/xmpp-incident</link><description>&lt;p&gt;(If you just want some recommendations for what to do, &lt;a href="#recommendations"&gt;skip down to the Recommendations section below&lt;/a&gt;.)&lt;/p&gt;</description><author>devever.net/~hl</author><pubDate>Fri, 20 Oct 2023 23:30:00 GMT</pubDate><guid isPermaLink="true">https://www.devever.net/~hl/xmpp-incident</guid></item><item><title>How I Track My Resume in Git</title><link>https://justingarrison.com/blog/2023-10-20-how-i-track-my-resume-with-git/</link><description>My workflow for resume and application tracking with branches based on roles and companies.</description><author>Justin Garrison's Homepage</author><pubDate>Fri, 20 Oct 2023 21:20:57 GMT</pubDate><guid isPermaLink="true">https://justingarrison.com/blog/2023-10-20-how-i-track-my-resume-with-git/</guid></item><item><title>Suggested readings week 42/2023</title><link>https://www.stefanocosta.org/suggested-readings-week-42-2023/</link><description>&lt;hr /&gt;&lt;h3 id="future-quantum-codebreakers-goldmining-from-the-past"&gt;&lt;a href="https://amp-theguardian-com.cdn.ampproject.org/c/s/amp.theguardian.com/commentisfree/2023/oct/07/encryption-services-quantum-computers-cryptography-rsa-signal-whatsapp?ref=stefanocosta.org" rel="nofollow noreferrer noopener"&gt;Future quantum codebreakers goldmining from the past&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;it&amp;#x2019;s possible that some bad actors are already hoarding encrypted messages in anticipation of being able to break them when a suitable quantum machine arrives&lt;/p&gt;&lt;p&gt;Wouldn&amp;apos;t you do that? The timeline for a widely available quantum computing capability is&lt;/p&gt;</description><author>Food for Thought (by Stefano Costa)</author><pubDate>Fri, 20 Oct 2023 19:59:03 GMT</pubDate><guid isPermaLink="true">https://www.stefanocosta.org/suggested-readings-week-42-2023/</guid></item><item><title>A Bit of SaaS Weekly: One Last Time</title><link>https://ethanmick.com/a-bit-of-saas-weekly-one-last-time/</link><description>&lt;p&gt;This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by &lt;a href="https://ethanmick.com/"&gt;Ethan Mick&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;This will be the last edition of the SaaS Weekly newsletter.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Thank you everyone for your support, comments, thoughts, and time. If you need me, you can always reply to&lt;/p&gt;</description><author>Build SaaS with Ethan</author><pubDate>Fri, 20 Oct 2023 18:36:38 GMT</pubDate><guid isPermaLink="true">https://ethanmick.com/a-bit-of-saas-weekly-one-last-time/</guid></item><item><title>Testing complex XState machines</title><link>https://blog.martijnarts.com/testing-complex-xstate-machines/</link><description>&lt;p&gt;I love using state machines in my frontends, and the current winner library to do it is XState. State machines (or actually state charts) can get quite complex, and like with everything I&amp;apos;ve developed some specific ways to test it. I&amp;apos;ve collected some guidelines and tricks&lt;/p&gt;</description><author>Martijn Arts</author><pubDate>Fri, 20 Oct 2023 13:04:18 GMT</pubDate><guid isPermaLink="true">https://blog.martijnarts.com/testing-complex-xstate-machines/</guid></item><item><title>Writing in Typst</title><link>https://lagomor.ph/indieweb/20231019/2151-bookmark/</link><description/><author>Home on Lagomorph</author><pubDate>Fri, 20 Oct 2023 06:52:00 GMT</pubDate><guid isPermaLink="true">https://lagomor.ph/indieweb/20231019/2151-bookmark/</guid></item><item><title>Zig is now also a Windows resource compiler</title><link>https://www.ryanliptak.com/blog/zig-is-a-windows-resource-compiler/</link><description>The latest development version of Zig can now compile (and cross-compile) Windows resource files and embed manifest files</description><author>ryanliptak.com</author><pubDate>Fri, 20 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.ryanliptak.com/blog/zig-is-a-windows-resource-compiler/</guid></item><item><title>2023-10-20</title><link>https://ho.dges.online/pictures/2023-10-20/</link><description/><author>ho.dges.online</author><pubDate>Fri, 20 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ho.dges.online/pictures/2023-10-20/</guid></item><item><title>On Being a Modern Software Explorer</title><link>https://www.danstroot.com/posts/2023-10-20-modern-software-explorer</link><description>&lt;img alt="post image" src="https://danstroot.imgix.net/assets/blog/img/2023-js-libraries.png" /&gt;&lt;br /&gt;&lt;br /&gt;When hiring software engneers I look beyond technical talent. What I really want is someone who understands "the ecosystem" they work in. Recruiting is always challenging. I think this is a good start to help you spot the great ones.&lt;br /&gt;&lt;br /&gt;This post &lt;a href="https://www.danstroot.com/posts/2023-10-20-modern-software-explorer"&gt;On Being a Modern Software Explorer&lt;/a&gt; first appeared on &lt;a href="https://www.danstroot.com"&gt;Dan Stroot's Blog&lt;/a&gt;</description><author>Dan Stroot</author><pubDate>Fri, 20 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.danstroot.com/posts/2023-10-20-modern-software-explorer</guid></item><item><title>Update: CRT modelines and EDID editing</title><link>https://nyanpasu64.gitlab.io/blog/crt-modeline-2023-10/</link><description>&lt;p&gt;I've released a major update to my article, "&lt;a href="/blog/crt-modeline-cvt-interlacing/"&gt;A deep dive into CRT modelines and EDID editing&lt;/a&gt;". I've expanded on getting a VGA output using HDMI-to-VGA adapters (DACs), reading, writing, and editing EDID data on Windows, Linux, and Mac, and tuning modelines in CRU to improve geometry and sharpness. If you're interested in using CRT monitors on computers, be sure to check it out!&lt;/p&gt;</description><author>nyanpasu64's blog</author><pubDate>Fri, 20 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://nyanpasu64.gitlab.io/blog/crt-modeline-2023-10/</guid></item><item><title>Automating Callbacks to Async/Await Migrations for Mocha Tests: Part 2</title><link>https://marcolabarile.me/my%20projects/2023/10/19/automating-callbacks-to-async-migration-in-mocha-tests-2/</link><description>In this second part we will go deeper into AST editing techniques and show how to automatically migrate tests that use the supertest NPM library.</description><author>MARCO LABARILE</author><pubDate>Fri, 20 Oct 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://marcolabarile.me/my%20projects/2023/10/19/automating-callbacks-to-async-migration-in-mocha-tests-2/</guid></item><item><title>You can now follow this blog on the fediverse</title><link>https://blog.ovalerio.net/archives/2654</link><description>The possibilities of the ActivityPub protocol, and what it can bring to the table regarding interoperability in the social media landscape, are immense. It is specially welcome after a decade (and half?) plagued by the dominance of centralized walled gardens that almost eradicated the diverse ecosystem that previously existed. It is used by many software [&amp;#8230;]</description><author>Gonçalo Valério</author><pubDate>Thu, 19 Oct 2023 21:45:03 GMT</pubDate><guid isPermaLink="true">https://blog.ovalerio.net/archives/2654</guid></item><item><title>Roast #31: V4 Survey Results</title><link>https://thoughtfulcoffeenyc.substack.com/p/roast-31-v4-survey-results</link><description>Feedback is the breakfast of champions</description><author>thoughtfulcoffee</author><pubDate>Thu, 19 Oct 2023 21:44:59 GMT</pubDate><guid isPermaLink="true">https://thoughtfulcoffeenyc.substack.com/p/roast-31-v4-survey-results</guid></item><item><title>Just paying Figma $15/month because nothing else fucking works</title><link>https://fasterthanli.me/articles/just-paying-figma-15-dollars</link><description>&lt;p&gt;My family wasn’t poor by any stretch of the imagination, but I was raised to
avoid spending money whenever possible.&lt;/p&gt;

&lt;p&gt;I was also taught “it’s a poor craftsman that blames their tools”, which
apparently means “take responsibility for your fuckups”, but, to young-me,
definitely sounded more like “you don’t deserve nice things”.&lt;/p&gt;

&lt;p&gt;I was also taught from an early age that I was born a sinner, incapable of doing
good by myself, and that all the earthly things were temptations, sent by the
devil to corrupt me (further I guess?) but also temporary, and that I shouldn’t
attach myself.&lt;/p&gt;






































&lt;a class="anchor" href="#draw-io-the-good" id="draw-io-the-good"&gt;&lt;/a&gt;























































&lt;a class="anchor" href="#draw-io-the-bad" id="draw-io-the-bad"&gt;&lt;/a&gt;












































&lt;a class="anchor" href="#figma" id="figma"&gt;&lt;/a&gt;






























&lt;a class="anchor" href="#the-worst-possible-business-strategy-protestant-ethics" id="the-worst-possible-business-strategy-protestant-ethics"&gt;&lt;/a&gt;


























&lt;a class="anchor" href="#penpot" id="penpot"&gt;&lt;/a&gt;










































































































































&lt;a class="anchor" href="#closing-words" id="closing-words"&gt;&lt;/a&gt;</description><author>fasterthanli.me</author><pubDate>Thu, 19 Oct 2023 19:50:00 GMT</pubDate><guid isPermaLink="true">https://fasterthanli.me/articles/just-paying-figma-15-dollars</guid></item><item><title>Passkey Sign In with Elixir and Phoenix</title><link>https://shadowfacts.net/2023/phoenix-passkeys/</link><description>&lt;p&gt;Passkeys are a replacement for passwords that use public/private cryptographic key pairs for login in a way that can be more user-friendly and resistant to a number of kinds of attacks. Let’s implement account registration and login with passkeys in a simple Phoenix web app.&lt;/p&gt;
&lt;p&gt;This work is heavily based on &lt;a href="https://www.imperialviolet.org/2022/09/22/passkeys.html"&gt;this article&lt;/a&gt; by Adam Langley, which provides a great deal of information about implementing passkeys. My goal here is to fill in some more of the details, and provide some Elixir-specific information. As with Adam’s article, I’m not going to use any WebAuthn libraries (even though that may be advisable from a security/maintenance perspective) since I think it’s interesting and helpful to understand how things actually work.&lt;/p&gt;
&lt;p&gt;Providing an exhaustive, production-ready implementation is a non-goal of this post. I’m going to make some slightly odd decisions for pedagogical reasons, and leave some things incomplete. That said, I’ll try to note when I’m doing so.&lt;/p&gt;
&lt;!-- excerpt-end --&gt;
&lt;p&gt;To start, I’m using the default Phoenix template app (less Tailwind) in which I’ve also generated the default, controller-based &lt;a href="https://hexdocs.pm/phoenix/mix_phx_gen_auth.html"&gt;authentication system&lt;/a&gt;. Some parts of the password-specific stuff have been stripped out altogether, others will get changed to fit with the passkey authentication setup we’re going to build.&lt;/p&gt;
&lt;h2&gt;Database schema&lt;/h2&gt;
&lt;p&gt;The first thing we’ll need is a backend schema for passkeys. The only data we need to store for a particular passkey is a unique identifier, and the public key itself. We also want users to be able to have multiple passkeys associated with their account (since they may want to login from multiple platforms that don’t cross-sync passkeys), so the users schema will have a one-to-many relationship with the passkeys.&lt;/p&gt;
&lt;aside&gt;
&lt;p&gt;This is one of those places I’m leaving things incomplete. What we’ll build will support having multiple passkeys tied to a single account, and let users log in with any of them. But I’m not going to implement the actual UI for adding additional ones—that’s left as an exercise for the reader ;)&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;The actual model I’ll call &lt;code&gt;UserCredential&lt;/code&gt;, since that’s closer to what the WebAuthn spec calls them. Here’s the schema, it’s pretty simple:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;defmodule&lt;/span&gt; &lt;span class="hl-mod"&gt;PhoenixPasskeys.Accounts.UserCredential&lt;/span&gt; &lt;span class="hl-kw"&gt;do&lt;/span&gt;
  &lt;span class="hl-kw"&gt;use&lt;/span&gt; &lt;span class="hl-mod"&gt;Ecto.Schema&lt;/span&gt;
  &lt;span class="hl-kw"&gt;import&lt;/span&gt; &lt;span class="hl-mod"&gt;Ecto.Changeset&lt;/span&gt;

  &lt;span class="hl-attr"&gt;@&lt;/span&gt;&lt;span class="hl-attr"&gt;primary_key&lt;/span&gt; {&lt;span class="hl-str"&gt;:id&lt;/span&gt;, &lt;span class="hl-str"&gt;:binary&lt;/span&gt;, []}

  &lt;span class="hl-fn"&gt;schema&lt;/span&gt; &lt;span class="hl-str"&gt;"users_credentials"&lt;/span&gt; &lt;span class="hl-kw"&gt;do&lt;/span&gt;
    &lt;span class="hl-cmt"&gt;# DER-encoded Subject Public Key Info: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.7&lt;/span&gt;
    &lt;span class="hl-fn"&gt;field&lt;/span&gt; &lt;span class="hl-str"&gt;:public_key_spki&lt;/span&gt;, &lt;span class="hl-str"&gt;:binary&lt;/span&gt;
    &lt;span class="hl-fn"&gt;belongs_to&lt;/span&gt; &lt;span class="hl-str"&gt;:user&lt;/span&gt;, &lt;span class="hl-mod"&gt;PhoenixPasskeys.Accounts.User&lt;/span&gt;
    &lt;span class="hl-fn"&gt;timestamps&lt;/span&gt;()
  &lt;span class="hl-kw"&gt;end&lt;/span&gt;

  &lt;span class="hl-kw"&gt;def&lt;/span&gt; &lt;span class="hl-fn"&gt;changeset&lt;/span&gt;(credential, attrs) &lt;span class="hl-kw"&gt;do&lt;/span&gt;
    credential
    &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-fn"&gt;cast&lt;/span&gt;(attrs, [&lt;span class="hl-str"&gt;:id&lt;/span&gt;, &lt;span class="hl-str"&gt;:public_key_spki&lt;/span&gt;, &lt;span class="hl-str"&gt;:user_id&lt;/span&gt;])
  &lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are a couple things to note here:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;First, we’re explicitly specifying that the primary key is a binary, since that’s what the WebAuthn API provides as the credential ID.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;public_key_spki&lt;/code&gt; field contains the data for the public key itself and the algorithm being used. We don’t care about the specific format, though, since we don’t have to parse it ourselves.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To the generated &lt;code&gt;User&lt;/code&gt; schema, I also added the &lt;code&gt;has_many&lt;/code&gt; side of the relationship. There’s also a migration to create the credentials table. I won’t show it here, since it’s exactly what you’d expect—just make sure the &lt;code&gt;id&lt;/code&gt; column is a binary.&lt;/p&gt;
&lt;h2&gt;Registration JavaScript&lt;/h2&gt;
&lt;p&gt;WebAuthn, being a modern web API, is a JavaScript API. This is a distinct disadvantage, if your users expect to be able to login from JavaScript-less browsers. Nonetheless, we proceed with the JS. Here’s the first bit:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-builtin"&gt;document&lt;/span&gt;.&lt;span class="hl-fn"&gt;addEventListener&lt;/span&gt;(&lt;span class="hl-str"&gt;"DOMContentLoaded"&lt;/span&gt;, () &lt;span class="hl-op"&gt;=&amp;gt;&lt;/span&gt; {
    &lt;span class="hl-kw"&gt;const&lt;/span&gt; registrationForm &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-builtin"&gt;document&lt;/span&gt;.&lt;span class="hl-fn"&gt;getElementById&lt;/span&gt;(&lt;span class="hl-str"&gt;"registration-form"&lt;/span&gt;);
    &lt;span class="hl-kw"&gt;if&lt;/span&gt; (registrationForm) {
        registrationForm.&lt;span class="hl-fn"&gt;addEventListener&lt;/span&gt;(&lt;span class="hl-str"&gt;"submit"&lt;/span&gt;, (event) &lt;span class="hl-op"&gt;=&amp;gt;&lt;/span&gt; {
            event.&lt;span class="hl-fn"&gt;preventDefault&lt;/span&gt;();
            &lt;span class="hl-fn"&gt;registerWebAuthnAccount&lt;/span&gt;(registrationForm);
        });
    }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we find the registration &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; element (note that I’ve added an ID (and also removed the password field)), we add a submit listener to it which prevents the form submission and instead calls the &lt;code&gt;registerWebAuthnAccount&lt;/code&gt; function we’ll create next.&lt;/p&gt;
&lt;p&gt;Before we get there, we’ll also write a brief helper function to check whether passkeys are actually available:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;async&lt;/span&gt; &lt;span class="hl-kw"&gt;function&lt;/span&gt; &lt;span class="hl-fn"&gt;supportsPasskeys&lt;/span&gt;() {
    &lt;span class="hl-kw"&gt;if&lt;/span&gt; (&lt;span class="hl-op"&gt;!&lt;/span&gt;&lt;span class="hl-builtin"&gt;window&lt;/span&gt;.&lt;span class="hl-prop"&gt;PublicKeyCredential&lt;/span&gt; &lt;span class="hl-op"&gt;||&lt;/span&gt; &lt;span class="hl-op"&gt;!&lt;/span&gt;PublicKeyCredential.&lt;span class="hl-prop"&gt;isConditionalMediationAvailable&lt;/span&gt;) {
		&lt;span class="hl-kw"&gt;return&lt;/span&gt; &lt;span class="hl-const"&gt;false&lt;/span&gt;;
	}
	&lt;span class="hl-kw"&gt;const&lt;/span&gt; [conditional, userVerifiying] &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-kw"&gt;await&lt;/span&gt; Promise.&lt;span class="hl-fn"&gt;all&lt;/span&gt;([
		PublicKeyCredential.&lt;span class="hl-fn"&gt;isConditionalMediationAvailable&lt;/span&gt;(),
		PublicKeyCredential.&lt;span class="hl-fn"&gt;isUserVerifyingPlatformAuthenticatorAvailable&lt;/span&gt;(),
	]);
	&lt;span class="hl-kw"&gt;return&lt;/span&gt; conditional &lt;span class="hl-op"&gt;&amp;amp;&amp;amp;&lt;/span&gt; userVerifiying;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The conditional mediation check establishes that WebAuthn conditional UI is available. This is what we’ll use for login, and is part of what makes using passkeys a nice, slick experience. Conditional UI will let us start a request for a passkey that doesn’t present anything until the user interacts with the browser’s passkey autofill UI.&lt;/p&gt;
&lt;p&gt;The user-verifying platform authenticator check establishes that, well, there is a user-verifying platform authenticator. The platform authenticator part means an authenticator that’s part of the user’s device, not removable, and the user-verifying part means that the authenticator verifies the presence of the user (such as via biometrics).&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;registerWebAuthnAccount&lt;/code&gt; function, the first thing we need to do is check that both these conditions are met and passkeys are supported by the browser. If not, we’ll just bail out and registration won’t be possible.&lt;/p&gt;
&lt;aside&gt;
&lt;p&gt;What you’d want to in an actual application depends on your circumstances. If you only want users to be able to register with passkeys, you should indicate that somehow, rather than failing silently as I’m doing here. Otherwise, you could fall back to an existing password-based registration flow.&lt;/p&gt;
&lt;/aside&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;async&lt;/span&gt; &lt;span class="hl-kw"&gt;function&lt;/span&gt; &lt;span class="hl-fn"&gt;registerWebAuthnAccount&lt;/span&gt;(form) {
    &lt;span class="hl-kw"&gt;if&lt;/span&gt; (&lt;span class="hl-op"&gt;!&lt;/span&gt;(&lt;span class="hl-kw"&gt;await&lt;/span&gt; &lt;span class="hl-fn"&gt;supportsPasskeys&lt;/span&gt;())) {
        &lt;span class="hl-kw"&gt;return&lt;/span&gt;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we’ll setup a big ol’ options object that we’ll pass to the WebAuthn API to specify what sort of credential we want. There’s going to be a whole lot of stuff here, so lets take it one piece at a time.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;async&lt;/span&gt; &lt;span class="hl-kw"&gt;function&lt;/span&gt; &lt;span class="hl-fn"&gt;registerWebAuthnAccount&lt;/span&gt;(form) {
    &lt;span class="hl-cmt"&gt;// ...&lt;/span&gt;
    &lt;span class="hl-kw"&gt;const&lt;/span&gt; createOptions &lt;span class="hl-op"&gt;=&lt;/span&gt; {
        &lt;span class="hl-prop"&gt;publicKey&lt;/span&gt;: {
			&lt;span class="hl-prop"&gt;rp&lt;/span&gt;: {
				&lt;span class="hl-prop"&gt;id&lt;/span&gt;: &lt;span class="hl-str"&gt;"localhost"&lt;/span&gt;,
				&lt;span class="hl-prop"&gt;name&lt;/span&gt;: &lt;span class="hl-str"&gt;"Phoenix Passkeys"&lt;/span&gt;,
			},
        },
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The RP is the Relying Party—that is, us, the party which relies on the credentials for authentication. The ID is the &lt;a href="https://w3c.github.io/webauthn/#rp-id"&gt;domain of the RP&lt;/a&gt;—just localhost for this example, though in reality you’d need to use your actual domain in production. The name is just a user-facing name for the RP.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;async&lt;/span&gt; &lt;span class="hl-kw"&gt;function&lt;/span&gt; &lt;span class="hl-fn"&gt;registerWebAuthnAccount&lt;/span&gt;(form) {
    &lt;span class="hl-cmt"&gt;// ...&lt;/span&gt;
    &lt;span class="hl-kw"&gt;const&lt;/span&gt; createOptions &lt;span class="hl-op"&gt;=&lt;/span&gt; {
        &lt;span class="hl-prop"&gt;publicKey&lt;/span&gt;: {
            &lt;span class="hl-cmt"&gt;// ...&lt;/span&gt;
            &lt;span class="hl-prop"&gt;user&lt;/span&gt;: {
                &lt;span class="hl-prop"&gt;id&lt;/span&gt;: &lt;span class="hl-fn"&gt;generateUserID&lt;/span&gt;(),
                &lt;span class="hl-prop"&gt;name&lt;/span&gt;: form[&lt;span class="hl-str"&gt;"user[email]"&lt;/span&gt;].&lt;span class="hl-prop"&gt;value&lt;/span&gt;,
                &lt;span class="hl-prop"&gt;displayName&lt;/span&gt;: &lt;span class="hl-str"&gt;""&lt;/span&gt;,
            },
        },
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next up is some info about the user who the credential is for. The user’s “name” will just be the email that they entered in the form. The &lt;code&gt;displayName&lt;/code&gt; value is required, per the spec, but we don’t have any other information so we just leave it blank and let the browser display the &lt;code&gt;name&lt;/code&gt; only. The ID is where this gets a little weird, since we’re just generating a random 64-byte value:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;function&lt;/span&gt; &lt;span class="hl-fn"&gt;generateUserID&lt;/span&gt;() {
	&lt;span class="hl-kw"&gt;const&lt;/span&gt; userID &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-kw"&gt;new&lt;/span&gt; Uint8Array(&lt;span class="hl-num"&gt;64&lt;/span&gt;);
	crypto.&lt;span class="hl-fn"&gt;getRandomValues&lt;/span&gt;(userID);
	&lt;span class="hl-kw"&gt;return&lt;/span&gt; userID;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you were adding a passkey to an existing user account, you could use a server-specified value for the user ID and the browser would replace any existing credentials with the same user ID and RP ID. But, since the user is registering a new account at this point, we assume they don’t want to do that (and, moreover, we don’t have any ID we can use yet). The user ID will also be returned upon a successful login, which would let us look up the user that’s logging in. However, since we’re only going to allow a credential to belong to a single user, we can use the credential’s ID to uniquely determine the user.&lt;/p&gt;
&lt;p&gt;Next up, we specify the types of public keys that we’ll accept. We’re going to support Ed25519, ES256, and RS256, since those are recommended by the WebAuthn spec:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;async&lt;/span&gt; &lt;span class="hl-kw"&gt;function&lt;/span&gt; &lt;span class="hl-fn"&gt;registerWebAuthnAccount&lt;/span&gt;(form) {
    &lt;span class="hl-cmt"&gt;// ...&lt;/span&gt;
    &lt;span class="hl-kw"&gt;const&lt;/span&gt; createOptions &lt;span class="hl-op"&gt;=&lt;/span&gt; {
        &lt;span class="hl-prop"&gt;publicKey&lt;/span&gt;: {
            &lt;span class="hl-cmt"&gt;// ...&lt;/span&gt;
            &lt;span class="hl-prop"&gt;pubKeyCredParams&lt;/span&gt;: [
                {&lt;span class="hl-prop"&gt;type&lt;/span&gt;: &lt;span class="hl-str"&gt;"public-key"&lt;/span&gt;, &lt;span class="hl-prop"&gt;alg&lt;/span&gt;: &lt;span class="hl-op"&gt;-&lt;/span&gt;&lt;span class="hl-num"&gt;8&lt;/span&gt;}, &lt;span class="hl-cmt"&gt;// Ed25519&lt;/span&gt;
                {&lt;span class="hl-prop"&gt;type&lt;/span&gt;: &lt;span class="hl-str"&gt;"public-key"&lt;/span&gt;, &lt;span class="hl-prop"&gt;alg&lt;/span&gt;: &lt;span class="hl-op"&gt;-&lt;/span&gt;&lt;span class="hl-num"&gt;7&lt;/span&gt;}, &lt;span class="hl-cmt"&gt;// ES256&lt;/span&gt;
                {&lt;span class="hl-prop"&gt;type&lt;/span&gt;: &lt;span class="hl-str"&gt;"public-key"&lt;/span&gt;, &lt;span class="hl-prop"&gt;alg&lt;/span&gt;: &lt;span class="hl-op"&gt;-&lt;/span&gt;&lt;span class="hl-num"&gt;257&lt;/span&gt;}, &lt;span class="hl-cmt"&gt;// RS256&lt;/span&gt;
            ],
        },
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;During the login process, the server will generate a challenge value that the client will sign and the server will verify was signed with the user’s private key. The spec also requires that we provide a challenge when creating a credential, but we’re not going to use it for anything (since the user is just now creating the credential, we have nothing trusted that we can verify the initial challenge against), so we just provide an empty value:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;async&lt;/span&gt; &lt;span class="hl-kw"&gt;function&lt;/span&gt; &lt;span class="hl-fn"&gt;registerWebAuthnAccount&lt;/span&gt;(form) {
    &lt;span class="hl-cmt"&gt;// ...&lt;/span&gt;
    &lt;span class="hl-kw"&gt;const&lt;/span&gt; createOptions &lt;span class="hl-op"&gt;=&lt;/span&gt; {
        &lt;span class="hl-prop"&gt;publicKey&lt;/span&gt;: {
            &lt;span class="hl-cmt"&gt;// ...&lt;/span&gt;
            &lt;span class="hl-prop"&gt;challenge&lt;/span&gt;: &lt;span class="hl-kw"&gt;new&lt;/span&gt; Uint8Array(),
        },
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lastly, we give our requirements for authenticators that we want to use:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;async&lt;/span&gt; &lt;span class="hl-kw"&gt;function&lt;/span&gt; &lt;span class="hl-fn"&gt;registerWebAuthnAccount&lt;/span&gt;(form) {
    &lt;span class="hl-cmt"&gt;// ...&lt;/span&gt;
    &lt;span class="hl-kw"&gt;const&lt;/span&gt; createOptions &lt;span class="hl-op"&gt;=&lt;/span&gt; {
        &lt;span class="hl-prop"&gt;publicKey&lt;/span&gt;: {
            &lt;span class="hl-cmt"&gt;// ...&lt;/span&gt;
            &lt;span class="hl-prop"&gt;authenticatorSelection&lt;/span&gt;: {
                &lt;span class="hl-prop"&gt;authenticatorAttachment&lt;/span&gt;: &lt;span class="hl-str"&gt;"platform"&lt;/span&gt;,
                &lt;span class="hl-prop"&gt;requireResidentKey&lt;/span&gt;: &lt;span class="hl-const"&gt;true&lt;/span&gt;,
            },
        },
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We want only the platform authenticator, not anything else, like removable security keys. We also specify that we want a resident key. This usage of “resident” is deprecated terminology that’s enshrined in the API. Really it means we want a &lt;em&gt;discoverable&lt;/em&gt; credential, so that the authenticator will surface it during the login process without us having to request the credential by ID. This is important to note, since it’s what prevents needing a separate username entry step.&lt;/p&gt;
&lt;p&gt;Now that we (finally) have all the configuration options in place, we can actually proceed with the credential creation. We pass the options to &lt;code&gt;navigator.credentials.create&lt;/code&gt; to actually create the WebAuthn credential. If that fails, we’ll just take the easy way out and alert to inform the user (in an actual service, you’d probably want better error handling).&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;async&lt;/span&gt; &lt;span class="hl-kw"&gt;function&lt;/span&gt; &lt;span class="hl-fn"&gt;registerWebAuthnAccount&lt;/span&gt;(form) {
    &lt;span class="hl-cmt"&gt;// ...&lt;/span&gt;
    &lt;span class="hl-kw"&gt;const&lt;/span&gt; credential &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-kw"&gt;await&lt;/span&gt; navigator.&lt;span class="hl-prop"&gt;credentials&lt;/span&gt;.&lt;span class="hl-fn"&gt;create&lt;/span&gt;(createOptions);
    &lt;span class="hl-kw"&gt;if&lt;/span&gt; (&lt;span class="hl-op"&gt;!&lt;/span&gt;credential) {
        &lt;span class="hl-fn"&gt;alert&lt;/span&gt;(&lt;span class="hl-str"&gt;"Could not create credential"&lt;/span&gt;);
        &lt;span class="hl-kw"&gt;return&lt;/span&gt;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From the credential we get back, we need a few pieces of information. First is the decoded client data, which is an object that contains information about the credential creation request that occurred.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;async&lt;/span&gt; &lt;span class="hl-kw"&gt;function&lt;/span&gt; &lt;span class="hl-fn"&gt;registerWebAuthnAccount&lt;/span&gt;(form) {
    &lt;span class="hl-cmt"&gt;// ...&lt;/span&gt;
    &lt;span class="hl-kw"&gt;const&lt;/span&gt; clientDataJSON &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-kw"&gt;new&lt;/span&gt; TextDecoder().&lt;span class="hl-fn"&gt;decode&lt;/span&gt;(credential.&lt;span class="hl-prop"&gt;response&lt;/span&gt;.&lt;span class="hl-prop"&gt;clientDataJSON&lt;/span&gt;);
    &lt;span class="hl-kw"&gt;const&lt;/span&gt; clientData &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-const"&gt;JSON&lt;/span&gt;.&lt;span class="hl-fn"&gt;parse&lt;/span&gt;(clientDataJSON);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;clientDataJSON&lt;/code&gt; field of the response object contains the JSON-serialized object in an &lt;code&gt;ArrayBuffer&lt;/code&gt;, so we decode that to text and then parse the JSON. With the decoded object, we do a consistency check with a couple pieces of data: the type of the request, whether or not it was cross-origin, and the actual origin being used.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;async&lt;/span&gt; &lt;span class="hl-kw"&gt;function&lt;/span&gt; &lt;span class="hl-fn"&gt;registerWebAuthnAccount&lt;/span&gt;(form) {
    &lt;span class="hl-cmt"&gt;// ...&lt;/span&gt;
    &lt;span class="hl-kw"&gt;if&lt;/span&gt; (clientData.&lt;span class="hl-prop"&gt;type&lt;/span&gt; &lt;span class="hl-op"&gt;!==&lt;/span&gt; &lt;span class="hl-str"&gt;"webauthn.create"&lt;/span&gt; &lt;span class="hl-op"&gt;||&lt;/span&gt; ((&lt;span class="hl-str"&gt;"crossOrigin"&lt;/span&gt; &lt;span class="hl-kw"&gt;in&lt;/span&gt; clientData) &lt;span class="hl-op"&gt;&amp;amp;&amp;amp;&lt;/span&gt; clientData.&lt;span class="hl-prop"&gt;crossOrigin&lt;/span&gt;) &lt;span class="hl-op"&gt;||&lt;/span&gt; clientData.&lt;span class="hl-prop"&gt;origin&lt;/span&gt; &lt;span class="hl-op"&gt;!==&lt;/span&gt; &lt;span class="hl-str"&gt;"http://localhost:4000"&lt;/span&gt;) {
        &lt;span class="hl-fn"&gt;alert&lt;/span&gt;(&lt;span class="hl-str"&gt;"Invalid credential"&lt;/span&gt;);
        &lt;span class="hl-kw"&gt;return&lt;/span&gt;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If it was not a creation request, the request was cross-origin, or the origin doesn’t match, we bail out. Note that in production, the origin should be checked against the actual production origin, not localhost. And again, in reality you’d want better error handling than just an alert.&lt;/p&gt;
&lt;p&gt;Next, we need to get the authenticator data which is encoded in a binary format and pull a few pieces of data out of it. You can see the full format of the authenticator data &lt;a href="https://w3c.github.io/webauthn/#sctn-authenticator-data"&gt;in the spec&lt;/a&gt;, but the parts we’re interested in are the backed-up state and the credential ID, which is part of the &lt;a href="https://w3c.github.io/webauthn/#attested-credential-data"&gt;attested credential data&lt;/a&gt;.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;async&lt;/span&gt; &lt;span class="hl-kw"&gt;function&lt;/span&gt; &lt;span class="hl-fn"&gt;registerWebAuthnAccount&lt;/span&gt;(form) {
    &lt;span class="hl-cmt"&gt;// ...&lt;/span&gt;
    &lt;span class="hl-kw"&gt;const&lt;/span&gt; authenticatorData &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-kw"&gt;new&lt;/span&gt; Uint8Array(credential.&lt;span class="hl-prop"&gt;response&lt;/span&gt;.&lt;span class="hl-fn"&gt;getAuthenticatorData&lt;/span&gt;());
    &lt;span class="hl-kw"&gt;const&lt;/span&gt; backedUp &lt;span class="hl-op"&gt;=&lt;/span&gt; (authenticatorData[&lt;span class="hl-num"&gt;32&lt;/span&gt;] &lt;span class="hl-op"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="hl-num"&gt;4&lt;/span&gt;) &lt;span class="hl-op"&gt;&amp;amp;&lt;/span&gt; &lt;span class="hl-num"&gt;1&lt;/span&gt;;
    &lt;span class="hl-kw"&gt;const&lt;/span&gt; idLength &lt;span class="hl-op"&gt;=&lt;/span&gt; (authenticatorData[&lt;span class="hl-num"&gt;53&lt;/span&gt;] &lt;span class="hl-op"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="hl-num"&gt;8&lt;/span&gt;) &lt;span class="hl-op"&gt;|&lt;/span&gt; authenticatorData[&lt;span class="hl-num"&gt;54&lt;/span&gt;];
    &lt;span class="hl-kw"&gt;const&lt;/span&gt; id &lt;span class="hl-op"&gt;=&lt;/span&gt; authenticatorData.&lt;span class="hl-fn"&gt;slice&lt;/span&gt;(&lt;span class="hl-num"&gt;55&lt;/span&gt;, &lt;span class="hl-num"&gt;55&lt;/span&gt; &lt;span class="hl-op"&gt;+&lt;/span&gt; idLength);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We get the backed-up bit from the flags byte at offset 32. Then we get the length of the credential ID, which is encoded as a big-endian, 16-bit integer at bytes 53 and 54. The ID itself immediately follows the length, thus starting at byte 55.&lt;/p&gt;
&lt;p&gt;Before proceeding, we check that the &lt;a href="https://w3c.github.io/webauthn/#sctn-credential-backup"&gt;backed-up bit&lt;/a&gt; is set, indicating that the credential is backed-up and safe from the user losing the current device. If it’s not, we won’t let the user register with this passkey. I choose to do this since it’s recommended by Adam Langley’s blog post, but whether it’s actually necessary may depend on your specific circumstances.&lt;/p&gt;
&lt;aside&gt;
&lt;p&gt;It seems like if we choose to block the user from registering with a passkey that’s already been generated, that passkey should be removed—since it won’t actually let them log in, and it may be confusing if it sticks around. But there doesn’t seem to be API to do that, so oh well.&lt;/p&gt;
&lt;/aside&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;async&lt;/span&gt; &lt;span class="hl-kw"&gt;function&lt;/span&gt; &lt;span class="hl-fn"&gt;registerWebAuthnAccount&lt;/span&gt;(form) {
    &lt;span class="hl-cmt"&gt;// ...&lt;/span&gt;
    &lt;span class="hl-kw"&gt;if&lt;/span&gt; (backedUp &lt;span class="hl-op"&gt;!==&lt;/span&gt; &lt;span class="hl-num"&gt;1&lt;/span&gt;) {
        &lt;span class="hl-fn"&gt;alert&lt;/span&gt;(&lt;span class="hl-str"&gt;"Can't register with non backed-up credential"&lt;/span&gt;);
        &lt;span class="hl-kw"&gt;return&lt;/span&gt;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The last piece of data we need out of the credential is the actual public key:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;async&lt;/span&gt; &lt;span class="hl-kw"&gt;function&lt;/span&gt; &lt;span class="hl-fn"&gt;registerWebAuthnAccount&lt;/span&gt;(form) {
    &lt;span class="hl-cmt"&gt;// ...&lt;/span&gt;
    &lt;span class="hl-kw"&gt;const&lt;/span&gt; publicKey &lt;span class="hl-op"&gt;=&lt;/span&gt; credential.&lt;span class="hl-prop"&gt;response&lt;/span&gt;.&lt;span class="hl-fn"&gt;getPublicKey&lt;/span&gt;();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And with all that in place, we can actually initiate the registration. We’ll assemble a form data payload with all of the requisite values:&lt;/p&gt;
&lt;aside&gt;
&lt;details&gt;

There's also a simple helper function for converting the two ArrayBuffer values to base 64 (click me to expand).

&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;function&lt;/span&gt; &lt;span class="hl-fn"&gt;arrayBufferToBase64&lt;/span&gt;(buf) {
	&lt;span class="hl-kw"&gt;let&lt;/span&gt; binary &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-str"&gt;""&lt;/span&gt;;
	&lt;span class="hl-kw"&gt;const&lt;/span&gt; bytes &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-kw"&gt;new&lt;/span&gt; Uint8Array(buf);
	&lt;span class="hl-kw"&gt;for&lt;/span&gt; (&lt;span class="hl-kw"&gt;let&lt;/span&gt; i &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-num"&gt;0&lt;/span&gt;; i &lt;span class="hl-op"&gt;&amp;lt;&lt;/span&gt; buf.&lt;span class="hl-prop"&gt;byteLength&lt;/span&gt;; i&lt;span class="hl-op"&gt;++&lt;/span&gt;) {
		binary &lt;span class="hl-op"&gt;+=&lt;/span&gt; String.&lt;span class="hl-fn"&gt;fromCharCode&lt;/span&gt;(bytes[i]);
	}
	&lt;span class="hl-kw"&gt;return&lt;/span&gt; &lt;span class="hl-fn"&gt;btoa&lt;/span&gt;(binary);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/details&gt;
&lt;/aside&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;async&lt;/span&gt; &lt;span class="hl-kw"&gt;function&lt;/span&gt; &lt;span class="hl-fn"&gt;registerWebAuthnAccount&lt;/span&gt;(form) {
    &lt;span class="hl-cmt"&gt;// ...&lt;/span&gt;
    &lt;span class="hl-kw"&gt;const&lt;/span&gt; body &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-kw"&gt;new&lt;/span&gt; FormData();
    body.&lt;span class="hl-fn"&gt;append&lt;/span&gt;(&lt;span class="hl-str"&gt;"_csrf_token"&lt;/span&gt;, form.&lt;span class="hl-prop"&gt;_csrf_token&lt;/span&gt;.&lt;span class="hl-prop"&gt;value&lt;/span&gt;);
    body.&lt;span class="hl-fn"&gt;append&lt;/span&gt;(&lt;span class="hl-str"&gt;"email"&lt;/span&gt;, form[&lt;span class="hl-str"&gt;"user[email]"&lt;/span&gt;].&lt;span class="hl-prop"&gt;value&lt;/span&gt;);
    body.&lt;span class="hl-fn"&gt;append&lt;/span&gt;(&lt;span class="hl-str"&gt;"credential_id"&lt;/span&gt;, &lt;span class="hl-fn"&gt;arrayBufferToBase64&lt;/span&gt;(id));
    body.&lt;span class="hl-fn"&gt;append&lt;/span&gt;(&lt;span class="hl-str"&gt;"public_key_spki"&lt;/span&gt;, &lt;span class="hl-fn"&gt;arrayBufferToBase64&lt;/span&gt;(publicKey));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We’ll use the body in a POST request to the registration endpoint. The response we get back will contain a status value to indicate whether the request was successful or not. If it was successful, the backend will have set the session cookie, and we can redirect to the home page and the new user will be logged in. If registration failed, we’ll alert the user.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;async&lt;/span&gt; &lt;span class="hl-kw"&gt;function&lt;/span&gt; &lt;span class="hl-fn"&gt;registerWebAuthnAccount&lt;/span&gt;(form) {
    &lt;span class="hl-cmt"&gt;// ...&lt;/span&gt;
    &lt;span class="hl-kw"&gt;const&lt;/span&gt; resp &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-kw"&gt;await&lt;/span&gt; &lt;span class="hl-fn"&gt;fetch&lt;/span&gt;(form.&lt;span class="hl-prop"&gt;action&lt;/span&gt;, {
        &lt;span class="hl-prop"&gt;method&lt;/span&gt;: &lt;span class="hl-str"&gt;"POST"&lt;/span&gt;,
        body,
    });
    &lt;span class="hl-kw"&gt;const&lt;/span&gt; respJSON &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-kw"&gt;await&lt;/span&gt; resp.&lt;span class="hl-fn"&gt;json&lt;/span&gt;();
    &lt;span class="hl-kw"&gt;if&lt;/span&gt; (respJSON.&lt;span class="hl-prop"&gt;status&lt;/span&gt; &lt;span class="hl-op"&gt;===&lt;/span&gt; &lt;span class="hl-str"&gt;"ok"&lt;/span&gt;) {
        &lt;span class="hl-builtin"&gt;window&lt;/span&gt;.&lt;span class="hl-prop"&gt;location&lt;/span&gt; &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-str"&gt;"/"&lt;/span&gt;;
    } &lt;span class="hl-kw"&gt;else&lt;/span&gt; {
        &lt;span class="hl-fn"&gt;alert&lt;/span&gt;(&lt;span class="hl-str"&gt;"Registration failed"&lt;/span&gt;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And with that, we’re done with JavaScript (for now) and can move on to the backend part of registering an account.&lt;/p&gt;
&lt;h2&gt;Creating a user&lt;/h2&gt;
&lt;p&gt;In the &lt;code&gt;UserRegistrationController&lt;/code&gt; module that comes with the Phoenix auth template, we’ll change the &lt;code&gt;create&lt;/code&gt; function. By default, it registers the user using the parameters from the signup form and then redirects to the homepage. Instead, we’re going to register using the passkey we created on the client and then respond with the JSON that our JavaScript is expecting.&lt;/p&gt;
&lt;p&gt;The first thing we need to do is extract the values that were sent by the frontend and decode the base 64-encoded ones.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;defmodule&lt;/span&gt; &lt;span class="hl-mod"&gt;PhoenixPasskeysWeb.UserRegistrationController&lt;/span&gt; &lt;span class="hl-kw"&gt;do&lt;/span&gt;
  &lt;span class="hl-kw"&gt;def&lt;/span&gt; &lt;span class="hl-fn"&gt;create&lt;/span&gt;(conn, %{
        &lt;span class="hl-str"&gt;"email"&lt;/span&gt; &lt;span class="hl-op"&gt;=&amp;gt;&lt;/span&gt; email,
        &lt;span class="hl-str"&gt;"credential_id"&lt;/span&gt; &lt;span class="hl-op"&gt;=&amp;gt;&lt;/span&gt; credential_id,
        &lt;span class="hl-str"&gt;"public_key_spki"&lt;/span&gt; &lt;span class="hl-op"&gt;=&amp;gt;&lt;/span&gt; public_key_spki
      }) &lt;span class="hl-kw"&gt;do&lt;/span&gt;
    credential_id &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-mod"&gt;Base&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;decode64!&lt;/span&gt;(credential_id)
    public_key_spki &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-mod"&gt;Base&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;decode64!&lt;/span&gt;(public_key_spki)
  &lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Those get passed to the &lt;code&gt;Accounts.register_user&lt;/code&gt; function, which we’ll update shortly to handle. If account creation succeeded, we’ll still send the confirmation email as the existing code did. After that, instead of redirecting, we’ll log the user in by setting the session cookie and then respond with the “ok” status for the frontend. If account creation fails, we’ll just respond with the “error” status so the frontend can alert the user.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;defmodule&lt;/span&gt; &lt;span class="hl-mod"&gt;PhoenixPasskeysWeb.UserRegistrationController&lt;/span&gt; &lt;span class="hl-kw"&gt;do&lt;/span&gt;
  &lt;span class="hl-kw"&gt;def&lt;/span&gt; &lt;span class="hl-fn"&gt;create&lt;/span&gt;(...) &lt;span class="hl-kw"&gt;do&lt;/span&gt;
    &lt;span class="hl-cmt"&gt;# ...&lt;/span&gt;
    &lt;span class="hl-kw"&gt;case&lt;/span&gt; &lt;span class="hl-mod"&gt;Accounts&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;register_user&lt;/span&gt;(email, credential_id, public_key_spki) &lt;span class="hl-kw"&gt;do&lt;/span&gt;
      {&lt;span class="hl-str"&gt;:ok&lt;/span&gt;, user} &lt;span class="hl-op"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="hl-cmt"&gt;# Send confirmation email...&lt;/span&gt;

        conn
        &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-mod"&gt;UserAuth&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;log_in_user_without_redirect&lt;/span&gt;(user)
        &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-fn"&gt;json&lt;/span&gt;(%{&lt;span class="hl-str"&gt;status: &lt;/span&gt;&lt;span class="hl-str"&gt;:ok&lt;/span&gt;})

      {&lt;span class="hl-str"&gt;:error&lt;/span&gt;, &lt;span class="hl-cmt"&gt;_changeset&lt;/span&gt;} &lt;span class="hl-op"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="hl-fn"&gt;json&lt;/span&gt;(conn, %{&lt;span class="hl-str"&gt;status: &lt;/span&gt;&lt;span class="hl-str"&gt;:error&lt;/span&gt;})
    &lt;span class="hl-kw"&gt;end&lt;/span&gt;
  &lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s update the &lt;code&gt;register_user&lt;/code&gt; function. Instead of just creating a changeset for the user and then inserting it, we need to also create the authenticator. To avoid potentially leaving things in a broken state, we wrap both of these in a database transaction.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;defmodule&lt;/span&gt; &lt;span class="hl-mod"&gt;PhoenixPasskeys.Accounts&lt;/span&gt; &lt;span class="hl-kw"&gt;do&lt;/span&gt;
  &lt;span class="hl-kw"&gt;def&lt;/span&gt; &lt;span class="hl-fn"&gt;register_user&lt;/span&gt;(email, credential_id, public_key_spki) &lt;span class="hl-kw"&gt;do&lt;/span&gt;
    &lt;span class="hl-mod"&gt;Repo&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;transaction&lt;/span&gt;(&lt;span class="hl-kw"&gt;fn&lt;/span&gt; &lt;span class="hl-op"&gt;-&amp;gt;&lt;/span&gt;
      user &lt;span class="hl-op"&gt;=&lt;/span&gt;
        %&lt;span class="hl-mod"&gt;User&lt;/span&gt;{}
        &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-mod"&gt;User&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;registration_changeset&lt;/span&gt;(%{&lt;span class="hl-str"&gt;email: &lt;/span&gt;email})
        &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-mod"&gt;Repo&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;insert&lt;/span&gt;()
        &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-kw"&gt;case&lt;/span&gt; &lt;span class="hl-kw"&gt;do&lt;/span&gt;
          {&lt;span class="hl-str"&gt;:ok&lt;/span&gt;, user} &lt;span class="hl-op"&gt;-&amp;gt;&lt;/span&gt; user
          {&lt;span class="hl-str"&gt;:error&lt;/span&gt;, changeset} &lt;span class="hl-op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="hl-mod"&gt;Repo&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;rollback&lt;/span&gt;(changeset)
        &lt;span class="hl-kw"&gt;end&lt;/span&gt;
    &lt;span class="hl-kw"&gt;end&lt;/span&gt;)
  &lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First, we create a user with the given email. If the user creation fails, we abort and rollback the transaction. Then, we can create a credential belonging to the new user with the credential ID and public key we received from the client. As with the user, if creating the credential fails, we rollback the transaction.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;defmodule&lt;/span&gt; &lt;span class="hl-mod"&gt;PhoenixPasskeys.Accounts&lt;/span&gt; &lt;span class="hl-kw"&gt;do&lt;/span&gt;
  &lt;span class="hl-kw"&gt;def&lt;/span&gt; &lt;span class="hl-fn"&gt;register_user&lt;/span&gt;(email, credential_id, public_key_spki) &lt;span class="hl-kw"&gt;do&lt;/span&gt;
    &lt;span class="hl-mod"&gt;Repo&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;transaction&lt;/span&gt;(&lt;span class="hl-kw"&gt;fn&lt;/span&gt; &lt;span class="hl-op"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="hl-cmt"&gt;# ...&lt;/span&gt;
      %&lt;span class="hl-mod"&gt;UserCredential&lt;/span&gt;{}
      &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-mod"&gt;UserCredential&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;changeset&lt;/span&gt;(%{
        &lt;span class="hl-str"&gt;id: &lt;/span&gt;credential_id,
        &lt;span class="hl-str"&gt;public_key_spki: &lt;/span&gt;public_key_spki,
        &lt;span class="hl-str"&gt;user_id: &lt;/span&gt;user&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-prop"&gt;id&lt;/span&gt;
      })
      &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-mod"&gt;Repo&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;insert&lt;/span&gt;()
      &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-kw"&gt;case&lt;/span&gt; &lt;span class="hl-kw"&gt;do&lt;/span&gt;
        {&lt;span class="hl-str"&gt;:ok&lt;/span&gt;, &lt;span class="hl-cmt"&gt;_credential&lt;/span&gt;} &lt;span class="hl-op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="hl-const"&gt;nil&lt;/span&gt;
        {&lt;span class="hl-str"&gt;:error&lt;/span&gt;, changeset} &lt;span class="hl-op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="hl-mod"&gt;Repo&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;rollback&lt;/span&gt;(changeset)
      &lt;span class="hl-kw"&gt;end&lt;/span&gt;
    &lt;span class="hl-kw"&gt;end&lt;/span&gt;)
  &lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We don’t need to do anything with the newly created credential, so we can just ignore it once it’s been created.&lt;/p&gt;
&lt;p&gt;And finally, from the transaction function, we return the user:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;defmodule&lt;/span&gt; &lt;span class="hl-mod"&gt;PhoenixPasskeys.Accounts&lt;/span&gt; &lt;span class="hl-kw"&gt;do&lt;/span&gt;
  &lt;span class="hl-kw"&gt;def&lt;/span&gt; &lt;span class="hl-fn"&gt;register_user&lt;/span&gt;(email, credential_id, public_key_spki) &lt;span class="hl-kw"&gt;do&lt;/span&gt;
    &lt;span class="hl-mod"&gt;Repo&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;transaction&lt;/span&gt;(&lt;span class="hl-kw"&gt;fn&lt;/span&gt; &lt;span class="hl-op"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="hl-cmt"&gt;# ...&lt;/span&gt;
      user
    &lt;span class="hl-kw"&gt;end&lt;/span&gt;)
  &lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The last thing we need to to complete the registration flow is to set the new user’s session cookie so that they’re logged in immediately. The &lt;code&gt;UserAuth&lt;/code&gt; module that’s generated as part of the Phoenix auth template has a &lt;code&gt;log_in_user&lt;/code&gt; function that does exactly this. But it also redirects the connection to another endpoint. We don’t want to do that, since we’re sending a JSON response, so I’ve split the function into two: one that only sets the session, and the existing function that sets the session and then redirects.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;defmodule&lt;/span&gt; &lt;span class="hl-mod"&gt;PhoenixPasskeysWeb.UserAuth&lt;/span&gt; &lt;span class="hl-kw"&gt;do&lt;/span&gt;
  &lt;span class="hl-kw"&gt;def&lt;/span&gt; &lt;span class="hl-fn"&gt;log_in_user&lt;/span&gt;(conn, user, params &lt;span class="hl-op"&gt;\\&lt;/span&gt; %{}) &lt;span class="hl-kw"&gt;do&lt;/span&gt;
    user_return_to &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-fn"&gt;get_session&lt;/span&gt;(conn, &lt;span class="hl-str"&gt;:user_return_to&lt;/span&gt;)
    conn
    &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-fn"&gt;log_in_user_without_redirect&lt;/span&gt;(user, params)
    &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-fn"&gt;redirect&lt;/span&gt;(&lt;span class="hl-str"&gt;to: &lt;/span&gt;user_return_to &lt;span class="hl-op"&gt;||&lt;/span&gt; &lt;span class="hl-fn"&gt;signed_in_path&lt;/span&gt;(conn))
  &lt;span class="hl-kw"&gt;end&lt;/span&gt;

  &lt;span class="hl-kw"&gt;def&lt;/span&gt; &lt;span class="hl-fn"&gt;log_in_user_without_redirect&lt;/span&gt;(conn, user, params &lt;span class="hl-op"&gt;\\&lt;/span&gt; %{}) &lt;span class="hl-kw"&gt;do&lt;/span&gt;
    token &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-mod"&gt;Accounts&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;generate_user_session_token&lt;/span&gt;(user)
    conn
    &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-fn"&gt;renew_session&lt;/span&gt;()
    &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-fn"&gt;put_token_in_session&lt;/span&gt;(token)
    &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-fn"&gt;maybe_write_remember_me_cookie&lt;/span&gt;(token, params)
  &lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And with that, everything is in place and you can now create an account with a passkey!&lt;/p&gt;
&lt;h2&gt;Login form&lt;/h2&gt;
&lt;p&gt;Now that the user’s got an account, they need to be able to login with it. That means once again interacting with the WebAuthn API and writing a bunch of JavaScript. But before we get there, we need some slight changes to the backend.&lt;/p&gt;
&lt;p&gt;In the HTML for the login page, we’ll add an ID to the &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; element so that we can find it from JS. We’ll also remove the password field, which is obviously no longer necessary. Lastly, but certainly not least, we need to send a challenge to the client.&lt;/p&gt;
&lt;p&gt;The challenge is a value that the user’s device will cryptographically sign with their private key. The result will get sent back to the server, and we’ll verify it against the public key we have stored thus authenticating them. We’ll send the challenge just in a hidden form field:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&amp;lt;&lt;span class="hl-tag"&gt;input&lt;/span&gt; &lt;span class="hl-attr"&gt;type&lt;/span&gt;="&lt;span class="hl-str"&gt;hidden&lt;/span&gt;" &lt;span class="hl-attr"&gt;id&lt;/span&gt;="&lt;span class="hl-str"&gt;challenge&lt;/span&gt;" &lt;span class="hl-attr"&gt;value&lt;/span&gt;=&lt;span class="hl-str"&gt;{@webauthn_challenge}&lt;/span&gt; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the session controller, we’ll need to generate and assign the challenge to the connection.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;defmodule&lt;/span&gt; &lt;span class="hl-mod"&gt;PhoenixPasskeysWeb.UserSessionController&lt;/span&gt; &lt;span class="hl-kw"&gt;do&lt;/span&gt;
  &lt;span class="hl-kw"&gt;def&lt;/span&gt; &lt;span class="hl-fn"&gt;new&lt;/span&gt;(conn, &lt;span class="hl-cmt"&gt;_params&lt;/span&gt;) &lt;span class="hl-kw"&gt;do&lt;/span&gt;
    conn
    &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-fn"&gt;put_webauthn_challenge&lt;/span&gt;()
    &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-fn"&gt;render&lt;/span&gt;(&lt;span class="hl-str"&gt;:new&lt;/span&gt;, &lt;span class="hl-str"&gt;error_message: &lt;/span&gt;&lt;span class="hl-const"&gt;nil&lt;/span&gt;)
  &lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;WebAuthn expects the challenge to be a value up to 64 bytes long, so we’ll use the Erlang crypto module to generate one of that length. The value is encoded as &lt;a href="https://datatracker.ietf.org/doc/html/rfc4648#section-5"&gt;URL-safe base 64&lt;/a&gt; (the same as normal base 64, but with dash and underscore rather than plus and slash) without padding. We encode it this way since that’s the format in which it will later be &lt;a href="https://w3c.github.io/webauthn/#dom-collectedclientdata-challenge"&gt;returned&lt;/a&gt; as part of the &lt;code&gt;clientDataJSON&lt;/code&gt;, so when we extract that value we can directly compare it to the challenge value we generated.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;defmodule&lt;/span&gt; &lt;span class="hl-mod"&gt;PhoenixPasskeysWeb.UserSessionController&lt;/span&gt; &lt;span class="hl-kw"&gt;do&lt;/span&gt;
  &lt;span class="hl-kw"&gt;defp&lt;/span&gt; &lt;span class="hl-fn"&gt;put_webauthn_challenge&lt;/span&gt;(conn) &lt;span class="hl-kw"&gt;do&lt;/span&gt;
    challenge &lt;span class="hl-op"&gt;=&lt;/span&gt;
      &lt;span class="hl-mod"&gt;:crypto&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;strong_rand_bytes&lt;/span&gt;(&lt;span class="hl-num"&gt;64&lt;/span&gt;)
      &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-mod"&gt;Base&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;url_encode64&lt;/span&gt;(&lt;span class="hl-str"&gt;padding: &lt;/span&gt;&lt;span class="hl-const"&gt;false&lt;/span&gt;)

    conn
    &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-fn"&gt;put_session&lt;/span&gt;(&lt;span class="hl-str"&gt;:webauthn_challenge&lt;/span&gt;, challenge)
    &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-fn"&gt;assign&lt;/span&gt;(&lt;span class="hl-str"&gt;:webauthn_challenge&lt;/span&gt;, challenge)
  &lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that the challenge string is also stored in the session, so that we can later check that the challenge that the client signed matches the challenge we generated. It’s safe to store this in the session, even though it’s sent to the client, because the cookie is encrypted and signed so the client can’t tamper with it.&lt;/p&gt;
&lt;h2&gt;Login JavaScript&lt;/h2&gt;
&lt;p&gt;With the first part of the backend changes taken care of, it’s time for more JavaScript, baby!&lt;/p&gt;
&lt;p&gt;We’ll follow a similar outline to the registration setup (and the same caveat applies about error handling).&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-builtin"&gt;document&lt;/span&gt;.&lt;span class="hl-fn"&gt;addEventListener&lt;/span&gt;(&lt;span class="hl-str"&gt;"DOMContentLoaded"&lt;/span&gt;, () &lt;span class="hl-op"&gt;=&amp;gt;&lt;/span&gt; {
    &lt;span class="hl-cmt"&gt;// ...&lt;/span&gt;
    &lt;span class="hl-kw"&gt;const&lt;/span&gt; loginForm &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-builtin"&gt;document&lt;/span&gt;.&lt;span class="hl-fn"&gt;getElementById&lt;/span&gt;(&lt;span class="hl-str"&gt;"login-form"&lt;/span&gt;);
	&lt;span class="hl-kw"&gt;if&lt;/span&gt; (loginForm) {
        &lt;span class="hl-fn"&gt;loginWebAuthnAccount&lt;/span&gt;(loginForm);
    }
});

&lt;span class="hl-kw"&gt;async&lt;/span&gt; &lt;span class="hl-kw"&gt;function&lt;/span&gt; &lt;span class="hl-fn"&gt;loginWebAuthnAccount&lt;/span&gt;(loginForm) {
    &lt;span class="hl-kw"&gt;if&lt;/span&gt; (&lt;span class="hl-op"&gt;!&lt;/span&gt;(&lt;span class="hl-kw"&gt;await&lt;/span&gt; &lt;span class="hl-fn"&gt;supportsPasskeys&lt;/span&gt;())) {
        &lt;span class="hl-kw"&gt;return&lt;/span&gt;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first thing we need to do is grab the challenge from the hidden form field, then we can construct the options object for getting the credential (which is, thankfully, much simpler than for creation).&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;async&lt;/span&gt; &lt;span class="hl-kw"&gt;function&lt;/span&gt; &lt;span class="hl-fn"&gt;loginWebAuthnAccount&lt;/span&gt;(loginForm) {
    &lt;span class="hl-cmt"&gt;// ...&lt;/span&gt;
    &lt;span class="hl-kw"&gt;const&lt;/span&gt; challenge &lt;span class="hl-op"&gt;=&lt;/span&gt; loginForm.&lt;span class="hl-prop"&gt;challenge&lt;/span&gt;.&lt;span class="hl-prop"&gt;value&lt;/span&gt;;
    &lt;span class="hl-kw"&gt;const&lt;/span&gt; getOptions &lt;span class="hl-op"&gt;=&lt;/span&gt; {
        &lt;span class="hl-prop"&gt;mediation&lt;/span&gt;: &lt;span class="hl-str"&gt;"conditional"&lt;/span&gt;,
        &lt;span class="hl-prop"&gt;publicKey&lt;/span&gt;: {
            &lt;span class="hl-prop"&gt;challenge&lt;/span&gt;: &lt;span class="hl-fn"&gt;base64URLToArrayBuffer&lt;/span&gt;(challenge),
            &lt;span class="hl-prop"&gt;rpId&lt;/span&gt;: &lt;span class="hl-str"&gt;"localhost"&lt;/span&gt;,
        },
    };
}
&lt;/code&gt;&lt;/pre&gt;&lt;aside&gt;
&lt;details&gt;

Here are the little helper functions that handle decoding from both regular and URL-safe base 64.

&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;function&lt;/span&gt; &lt;span class="hl-fn"&gt;base64URLToArrayBuffer&lt;/span&gt;(b64) {
	&lt;span class="hl-kw"&gt;const&lt;/span&gt; converted &lt;span class="hl-op"&gt;=&lt;/span&gt; b64.&lt;span class="hl-fn"&gt;replace&lt;/span&gt;(&lt;span class="hl-str"&gt;&lt;span class="hl-op"&gt;/&lt;/span&gt;[-_]&lt;span class="hl-op"&gt;/&lt;/span&gt;g&lt;/span&gt;, (c) &lt;span class="hl-op"&gt;=&amp;gt;&lt;/span&gt; c &lt;span class="hl-op"&gt;===&lt;/span&gt; &lt;span class="hl-str"&gt;"-"&lt;/span&gt; ? &lt;span class="hl-str"&gt;"+"&lt;/span&gt; : &lt;span class="hl-str"&gt;"/"&lt;/span&gt;);
	&lt;span class="hl-kw"&gt;return&lt;/span&gt; &lt;span class="hl-fn"&gt;base64ToArrayBuffer&lt;/span&gt;(converted);
}
&lt;span class="hl-kw"&gt;function&lt;/span&gt; &lt;span class="hl-fn"&gt;base64ToArrayBuffer&lt;/span&gt;(b64) {
	&lt;span class="hl-kw"&gt;const&lt;/span&gt; bin &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-fn"&gt;atob&lt;/span&gt;(b64);
	&lt;span class="hl-kw"&gt;const&lt;/span&gt; bytes &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-kw"&gt;new&lt;/span&gt; Uint8Array(bin.&lt;span class="hl-prop"&gt;length&lt;/span&gt;);
	&lt;span class="hl-kw"&gt;for&lt;/span&gt; (&lt;span class="hl-kw"&gt;let&lt;/span&gt; i &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-num"&gt;0&lt;/span&gt;; i &lt;span class="hl-op"&gt;&amp;lt;&lt;/span&gt; bin.&lt;span class="hl-prop"&gt;length&lt;/span&gt;; i&lt;span class="hl-op"&gt;++&lt;/span&gt;) {
		bytes[i] &lt;span class="hl-op"&gt;=&lt;/span&gt; bin.&lt;span class="hl-fn"&gt;charCodeAt&lt;/span&gt;(i);
	}
	&lt;span class="hl-kw"&gt;return&lt;/span&gt; bytes.&lt;span class="hl-prop"&gt;buffer&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/details&gt;
&lt;/aside&gt;
&lt;p&gt;In the options, we specify that we want conditional mediation. As noted before, this means that the browser won’t display any UI, except for autofill, for this credential request until the user accepts the autofill suggestion. In the public key options, we also give the decoded challenge value and specify the our Relying Party ID (again, this would need to be the actual domain in production).&lt;/p&gt;
&lt;p&gt;Now, we can actually make the credential request and then, if we get a credential back, encode and send all the values to the backend. We need to send the ID of the credential, so that the backend can find its public key and the corresponding user. We also need the client data JSON, which we send as text decoded from the &lt;code&gt;ArrayBuffer&lt;/code&gt; it’s returned as. We also need to send the authenticator data as well as the signature itself.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;async&lt;/span&gt; &lt;span class="hl-kw"&gt;function&lt;/span&gt; &lt;span class="hl-fn"&gt;loginWebAuthnAccount&lt;/span&gt;(loginForm) {
    &lt;span class="hl-cmt"&gt;// ...&lt;/span&gt;
    &lt;span class="hl-kw"&gt;const&lt;/span&gt; credential &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-kw"&gt;await&lt;/span&gt; navigator.&lt;span class="hl-prop"&gt;credentials&lt;/span&gt;.&lt;span class="hl-fn"&gt;get&lt;/span&gt;(getOptions);
    &lt;span class="hl-kw"&gt;if&lt;/span&gt; (&lt;span class="hl-op"&gt;!&lt;/span&gt;credential) {
        &lt;span class="hl-fn"&gt;alert&lt;/span&gt;(&lt;span class="hl-str"&gt;"Could not get credential"&lt;/span&gt;);
        &lt;span class="hl-kw"&gt;return&lt;/span&gt;;
    }

    &lt;span class="hl-kw"&gt;const&lt;/span&gt; clientDataJSON &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-kw"&gt;new&lt;/span&gt; TextDecoder().&lt;span class="hl-fn"&gt;decode&lt;/span&gt;(credential.&lt;span class="hl-prop"&gt;response&lt;/span&gt;.&lt;span class="hl-prop"&gt;clientDataJSON&lt;/span&gt;);

    &lt;span class="hl-kw"&gt;const&lt;/span&gt; body &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-kw"&gt;new&lt;/span&gt; FormData();
    body.&lt;span class="hl-fn"&gt;append&lt;/span&gt;(&lt;span class="hl-str"&gt;"_csrf_token"&lt;/span&gt;, loginForm.&lt;span class="hl-prop"&gt;_csrf_token&lt;/span&gt;.&lt;span class="hl-prop"&gt;value&lt;/span&gt;);
    body.&lt;span class="hl-fn"&gt;append&lt;/span&gt;(&lt;span class="hl-str"&gt;"raw_id"&lt;/span&gt;, &lt;span class="hl-fn"&gt;arrayBufferToBase64&lt;/span&gt;(credential.&lt;span class="hl-prop"&gt;rawId&lt;/span&gt;));
    body.&lt;span class="hl-fn"&gt;append&lt;/span&gt;(&lt;span class="hl-str"&gt;"client_data_json"&lt;/span&gt;, clientDataJSON);
    body.&lt;span class="hl-fn"&gt;append&lt;/span&gt;(&lt;span class="hl-str"&gt;"authenticator_data"&lt;/span&gt;, &lt;span class="hl-fn"&gt;arrayBufferToBase64&lt;/span&gt;(credential.&lt;span class="hl-prop"&gt;response&lt;/span&gt;.&lt;span class="hl-prop"&gt;authenticatorData&lt;/span&gt;));
    body.&lt;span class="hl-fn"&gt;append&lt;/span&gt;(&lt;span class="hl-str"&gt;"signature"&lt;/span&gt;, &lt;span class="hl-fn"&gt;arrayBufferToBase64&lt;/span&gt;(credential.&lt;span class="hl-prop"&gt;response&lt;/span&gt;.&lt;span class="hl-prop"&gt;signature&lt;/span&gt;));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can then send a request to the login endpoint. If the backend request fails (or if we failed to get the credential) we just alert the user (again, you’d probably want something better in reality). If the login attempt was successful, the server will have set the session cookie, and so we can just redirect to the homepage and the user will be logged in.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;async&lt;/span&gt; &lt;span class="hl-kw"&gt;function&lt;/span&gt; &lt;span class="hl-fn"&gt;loginWebAuthnAccount&lt;/span&gt;(loginForm) {
    &lt;span class="hl-cmt"&gt;// ...&lt;/span&gt;
    &lt;span class="hl-kw"&gt;const&lt;/span&gt; resp &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-kw"&gt;await&lt;/span&gt; &lt;span class="hl-fn"&gt;fetch&lt;/span&gt;(&lt;span class="hl-str"&gt;"/users/log_in"&lt;/span&gt;, {
        &lt;span class="hl-prop"&gt;method&lt;/span&gt;: &lt;span class="hl-str"&gt;"POST"&lt;/span&gt;,
        body,
    });
    &lt;span class="hl-kw"&gt;const&lt;/span&gt; respJSON &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-kw"&gt;await&lt;/span&gt; resp.&lt;span class="hl-fn"&gt;json&lt;/span&gt;();
    &lt;span class="hl-kw"&gt;if&lt;/span&gt; (respJSON?.&lt;span class="hl-prop"&gt;status&lt;/span&gt; &lt;span class="hl-op"&gt;===&lt;/span&gt; &lt;span class="hl-str"&gt;"ok"&lt;/span&gt;) {
        &lt;span class="hl-builtin"&gt;window&lt;/span&gt;.&lt;span class="hl-prop"&gt;location&lt;/span&gt; &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-str"&gt;"/"&lt;/span&gt;;
    } &lt;span class="hl-kw"&gt;else&lt;/span&gt; {
        &lt;span class="hl-fn"&gt;alert&lt;/span&gt;(&lt;span class="hl-str"&gt;"Login failed"&lt;/span&gt;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that in place, let’s move on to the backend half of the login request.&lt;/p&gt;
&lt;h2&gt;Validating a login attempt&lt;/h2&gt;
&lt;p&gt;As with signup, we’ll modify the existing log in endpoint to actually validate the WebAuthn login attempt.&lt;/p&gt;
&lt;p&gt;The first step is extracting all of the information the frontend provides in the params and decoding it:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;defmodule&lt;/span&gt; &lt;span class="hl-mod"&gt;PhoenixPasskeysWeb.UserSessionController&lt;/span&gt; &lt;span class="hl-kw"&gt;do&lt;/span&gt;
  &lt;span class="hl-kw"&gt;def&lt;/span&gt; &lt;span class="hl-fn"&gt;create&lt;/span&gt;(conn, params) &lt;span class="hl-kw"&gt;do&lt;/span&gt;
    id &lt;span class="hl-op"&gt;=&lt;/span&gt; params &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-mod"&gt;Map&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;get&lt;/span&gt;(&lt;span class="hl-str"&gt;"raw_id"&lt;/span&gt;) &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-mod"&gt;Base&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;decode64!&lt;/span&gt;()
    authenticator_data &lt;span class="hl-op"&gt;=&lt;/span&gt; params &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-mod"&gt;Map&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;get&lt;/span&gt;(&lt;span class="hl-str"&gt;"authenticator_data"&lt;/span&gt;) &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-mod"&gt;Base&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;decode64!&lt;/span&gt;()
    client_data_json_str &lt;span class="hl-op"&gt;=&lt;/span&gt; params &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-mod"&gt;Map&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;get&lt;/span&gt;(&lt;span class="hl-str"&gt;"client_data_json"&lt;/span&gt;)
    signature &lt;span class="hl-op"&gt;=&lt;/span&gt; params &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-mod"&gt;Map&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;get&lt;/span&gt;(&lt;span class="hl-str"&gt;"signature"&lt;/span&gt;) &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-mod"&gt;Base&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;decode64!&lt;/span&gt;()
  &lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we’re going to validate all of the information we got from the client. Before we get to that, there are a handful of helper functions we’ll use. First, looking up a credential by its ID:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;defmodule&lt;/span&gt; &lt;span class="hl-mod"&gt;PhoenixPasskeys.Accounts&lt;/span&gt; &lt;span class="hl-kw"&gt;do&lt;/span&gt;
  &lt;span class="hl-kw"&gt;def&lt;/span&gt; &lt;span class="hl-fn"&gt;get_credential&lt;/span&gt;(id) &lt;span class="hl-kw"&gt;do&lt;/span&gt;
    &lt;span class="hl-mod"&gt;Repo&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;get&lt;/span&gt;(&lt;span class="hl-mod"&gt;UserCredential&lt;/span&gt;, id)
    &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-mod"&gt;Repo&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;preload&lt;/span&gt;(&lt;span class="hl-str"&gt;:user&lt;/span&gt;)
  &lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, a function in the controller that verifies the signature against the provided data:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;defmodule&lt;/span&gt; &lt;span class="hl-mod"&gt;PhoenixPasskeysWeb.UserSessionController&lt;/span&gt; &lt;span class="hl-kw"&gt;do&lt;/span&gt;
  &lt;span class="hl-kw"&gt;defp&lt;/span&gt; &lt;span class="hl-fn"&gt;verify_signature&lt;/span&gt;(credential, client_data_json_str, authenticator_data, signature) &lt;span class="hl-kw"&gt;do&lt;/span&gt;
    &lt;span class="hl-kw"&gt;with&lt;/span&gt; {&lt;span class="hl-str"&gt;:ok&lt;/span&gt;, pubkey} &lt;span class="hl-op"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="hl-mod"&gt;X509.PublicKey&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;from_der&lt;/span&gt;(credential&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-prop"&gt;public_key_spki&lt;/span&gt;),
         client_data_json_hash &lt;span class="hl-op"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="hl-mod"&gt;:crypto&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;hash&lt;/span&gt;(&lt;span class="hl-str"&gt;:sha256&lt;/span&gt;, client_data_json_str),
         signed_message &lt;span class="hl-op"&gt;&amp;lt;-&lt;/span&gt; authenticator_data &lt;span class="hl-op"&gt;&amp;lt;&amp;gt;&lt;/span&gt; client_data_json_hash,
         &lt;span class="hl-const"&gt;true&lt;/span&gt; &lt;span class="hl-op"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="hl-mod"&gt;:public_key&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;verify&lt;/span&gt;(signed_message, &lt;span class="hl-str"&gt;:sha256&lt;/span&gt;, signature, pubkey) &lt;span class="hl-kw"&gt;do&lt;/span&gt;
      &lt;span class="hl-const"&gt;true&lt;/span&gt;
    &lt;span class="hl-kw"&gt;else&lt;/span&gt;
      &lt;span class="hl-cmt"&gt;_&lt;/span&gt; &lt;span class="hl-op"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="hl-const"&gt;false&lt;/span&gt;
    &lt;span class="hl-kw"&gt;end&lt;/span&gt;
  &lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;X509&lt;/code&gt; comes from the &lt;a href="https://hex.pm/packages/x509"&gt;&lt;code&gt;x509&lt;/code&gt;&lt;/a&gt; package, which is the only third-party piece of code we’re using. It’s a fairly thin wrapper around the Erlang &lt;code&gt;public_key&lt;/code&gt; and &lt;code&gt;crypto&lt;/code&gt; modules, and mostly serves to save me from having to deal with Erlang records in my code. Its &lt;code&gt;from_der&lt;/code&gt; helper function is used to parse the public key from the encoded format.&lt;/p&gt;
&lt;p&gt;Next, we hash the client data JSON and append that hash to the authenticator data from the client. This value is what should match the signature using the public key we’ve got, so finally we check that. If all these steps succeeded, we return true, and false otherwise.&lt;/p&gt;
&lt;p&gt;The last helper function will receive the decoded client data make sure it’s got all of the values that we expect. If the &lt;code&gt;crossOrigin&lt;/code&gt; value is present and is not false, the client data is invalid and the login attempt will be rejected.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;defmodule&lt;/span&gt; &lt;span class="hl-mod"&gt;PhoenixPasskeysWeb.UserSessionController&lt;/span&gt; &lt;span class="hl-kw"&gt;do&lt;/span&gt;
  &lt;span class="hl-kw"&gt;defp&lt;/span&gt; &lt;span class="hl-fn"&gt;check_client_data_json&lt;/span&gt;(%{&lt;span class="hl-str"&gt;"crossOrigin"&lt;/span&gt; &lt;span class="hl-op"&gt;=&amp;gt;&lt;/span&gt; crossOrigin}) &lt;span class="hl-kw"&gt;when&lt;/span&gt; crossOrigin &lt;span class="hl-op"&gt;!=&lt;/span&gt; &lt;span class="hl-const"&gt;false&lt;/span&gt; &lt;span class="hl-kw"&gt;do&lt;/span&gt;
    &lt;span class="hl-const"&gt;false&lt;/span&gt;
  &lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Otherwise, we check that the data has the expected type and origin, and we extract the challenge value (note that we’re checking the origin again here, and this would need to change in production):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;defmodule&lt;/span&gt; &lt;span class="hl-mod"&gt;PhoenixPasskeysWeb.UserSessionController&lt;/span&gt; &lt;span class="hl-kw"&gt;do&lt;/span&gt;
  &lt;span class="hl-kw"&gt;defp&lt;/span&gt; &lt;span class="hl-fn"&gt;check_client_data_json&lt;/span&gt;(%{
         &lt;span class="hl-str"&gt;"type"&lt;/span&gt; &lt;span class="hl-op"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="hl-str"&gt;"webauthn.get"&lt;/span&gt;,
         &lt;span class="hl-str"&gt;"challenge"&lt;/span&gt; &lt;span class="hl-op"&gt;=&amp;gt;&lt;/span&gt; challenge,
         &lt;span class="hl-str"&gt;"origin"&lt;/span&gt; &lt;span class="hl-op"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="hl-str"&gt;"http://localhost:4000"&lt;/span&gt;
       }) &lt;span class="hl-kw"&gt;do&lt;/span&gt;
    {&lt;span class="hl-str"&gt;:ok&lt;/span&gt;, challenge}
  &lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And lastly, if neither of the previous patterns matched, the client data fails validation:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;defmodule&lt;/span&gt; &lt;span class="hl-mod"&gt;PhoenixPasskeysWeb.UserSessionController&lt;/span&gt; &lt;span class="hl-kw"&gt;do&lt;/span&gt;
  &lt;span class="hl-kw"&gt;defp&lt;/span&gt; &lt;span class="hl-fn"&gt;check_client_data_json&lt;/span&gt;(&lt;span class="hl-cmt"&gt;_&lt;/span&gt;), &lt;span class="hl-str"&gt;do: &lt;/span&gt;&lt;span class="hl-const"&gt;false&lt;/span&gt;
&lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, let’s put all those parts together and validate the login attempt.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;defmodule&lt;/span&gt; &lt;span class="hl-mod"&gt;PhoenixPasskeysWeb.UserSessionController&lt;/span&gt; &lt;span class="hl-kw"&gt;do&lt;/span&gt;
  &lt;span class="hl-kw"&gt;def&lt;/span&gt; &lt;span class="hl-fn"&gt;create&lt;/span&gt;(conn, params) &lt;span class="hl-kw"&gt;do&lt;/span&gt;
    &lt;span class="hl-cmt"&gt;# ...&lt;/span&gt;
    &lt;span class="hl-kw"&gt;with&lt;/span&gt; credential &lt;span class="hl-kw"&gt;when&lt;/span&gt; &lt;span class="hl-kw"&gt;not&lt;/span&gt; &lt;span class="hl-fn"&gt;is_nil&lt;/span&gt;(credential) &lt;span class="hl-op"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="hl-mod"&gt;Accounts&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;get_credential&lt;/span&gt;(id),
         &lt;span class="hl-const"&gt;true&lt;/span&gt; &lt;span class="hl-op"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="hl-fn"&gt;verify_signature&lt;/span&gt;(credential, client_data_json_str, authenticator_data, signature),
         {&lt;span class="hl-str"&gt;:ok&lt;/span&gt;, client_data_json} &lt;span class="hl-op"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="hl-mod"&gt;Jason&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;decode&lt;/span&gt;(client_data_json_str),
         {&lt;span class="hl-str"&gt;:ok&lt;/span&gt;, challenge} &lt;span class="hl-op"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="hl-fn"&gt;check_client_data_json&lt;/span&gt;(client_data_json),
         &lt;span class="hl-const"&gt;true&lt;/span&gt; &lt;span class="hl-op"&gt;&amp;lt;-&lt;/span&gt; challenge &lt;span class="hl-op"&gt;==&lt;/span&gt; &lt;span class="hl-fn"&gt;get_session&lt;/span&gt;(conn, &lt;span class="hl-str"&gt;:webauthn_challenge&lt;/span&gt;),
         &lt;span class="hl-const"&gt;true&lt;/span&gt; &lt;span class="hl-op"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="hl-mod"&gt;:binary&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;part&lt;/span&gt;(authenticator_data, &lt;span class="hl-num"&gt;0&lt;/span&gt;, &lt;span class="hl-num"&gt;32&lt;/span&gt;) &lt;span class="hl-op"&gt;==&lt;/span&gt; &lt;span class="hl-mod"&gt;:crypto&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;hash&lt;/span&gt;(&lt;span class="hl-str"&gt;:sha256&lt;/span&gt;, &lt;span class="hl-str"&gt;"localhost"&lt;/span&gt;),
         &lt;span class="hl-const"&gt;true&lt;/span&gt; &lt;span class="hl-op"&gt;&amp;lt;-&lt;/span&gt; (&lt;span class="hl-mod"&gt;:binary&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;at&lt;/span&gt;(authenticator_data, &lt;span class="hl-num"&gt;32&lt;/span&gt;) &lt;span class="hl-op"&gt;&amp;amp;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="hl-num"&gt;1&lt;/span&gt;) &lt;span class="hl-op"&gt;==&lt;/span&gt; &lt;span class="hl-num"&gt;1&lt;/span&gt; &lt;span class="hl-kw"&gt;do&lt;/span&gt;
      conn
      &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-fn"&gt;delete_session&lt;/span&gt;(&lt;span class="hl-str"&gt;:webauthn_challenge&lt;/span&gt;)
      &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-mod"&gt;UserAuth&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;log_in_user_without_redirect&lt;/span&gt;(credential&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-prop"&gt;user&lt;/span&gt;)
      &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-fn"&gt;json&lt;/span&gt;(%{&lt;span class="hl-str"&gt;status: &lt;/span&gt;&lt;span class="hl-str"&gt;:ok&lt;/span&gt;})
    &lt;span class="hl-kw"&gt;else&lt;/span&gt;
      &lt;span class="hl-cmt"&gt;_&lt;/span&gt; &lt;span class="hl-op"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="hl-fn"&gt;json&lt;/span&gt;(conn, %{&lt;span class="hl-str"&gt;status: &lt;/span&gt;&lt;span class="hl-str"&gt;:error&lt;/span&gt;})
    &lt;span class="hl-kw"&gt;end&lt;/span&gt;
  &lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here’s everything that we’re doing:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Lookup the credential with the given ID.&lt;/li&gt;
&lt;li&gt;Use the public key we had stored to verify the signature on the authenticator data and client data JSON.&lt;/li&gt;
&lt;li&gt;Decode the client data JSON.&lt;/li&gt;
&lt;li&gt;Check that all the values in the client data are what we expect, and extract the challenge that was signed.&lt;/li&gt;
&lt;li&gt;Ensure the challenge that the user signed matches what we previously generated.&lt;/li&gt;
&lt;li&gt;Extract the hash of the origin from the &lt;a href="https://w3c.github.io/webauthn/#authenticator-data"&gt;authenticator data&lt;/a&gt; and ensure it matches our origin (this would not be localhost in production).&lt;/li&gt;
&lt;li&gt;Check that the authenticator data has the user presence bit set (indicating that a person was actually present on the client’s end).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If all of those steps succeeded, we remove the old challenge value from the session (since it’s no longer needed), actually log the user in, and then respond with the “ok” status that the JavaScript is expecting. If any step failed, we’ll respond with the “error” status and the frontend will alert the user.&lt;/p&gt;
&lt;p&gt;Since there’s a lot going on here, it’s worth being clear about what exactly in this process lets us authenticate and prove that the user is who they claim to be. Since the signature verification step is using the public key that we stored during the registration process, we know that anyone that can produce a valid signature using that public key must be the user (or at any rate, have their private key). The value that they’re signing is, essentially, the challenge: a securely generated random value. The user isn’t directly signing the challenge, but this is still safe, since the challenge value is included in the client data JSON, that hash of which is included in the signed message.&lt;/p&gt;
&lt;p&gt;So: the challenge value that was signed by the user must be in the client data, and the challenge value in the client data must be the one we generated. Given that, we know that the user whose key was used to sign the message is the one trying to log in now. That we’re verifying with the stored public key prevents an attacker from using an arbitrary key to sign the login attempt. And that the signed challenge matches the challenge the server generated means an attacker can’t reuse a previous response to login (a replay attack).&lt;/p&gt;
&lt;p&gt;At long last, we finally have the ability to log in to our application using a passkey. Only a few minor things to go, so let’s forge ahead.&lt;/p&gt;
&lt;h2&gt;Handling login if the user enters an email&lt;/h2&gt;
&lt;p&gt;Although we’re presenting the conditional UI, there’s nothing preventing the user from typing their email into the field and then clicking “Sign in,” so we should probably handle that to. This can be done fairly simply by reusing our existing code for conditional login.&lt;/p&gt;
&lt;p&gt;We’ll change the &lt;code&gt;loginWebAuthnAccount&lt;/code&gt; to take an additional parameter, &lt;code&gt;conditional&lt;/code&gt;, which will be a boolean indicating whether this login attempt is to setup the conditional UI or triggered by submitting the login form.&lt;/p&gt;
&lt;p&gt;If it’s false, we won’t request conditional mediation and instead we’ll look up the credentials corresponding to the email the user entered and ask WebAuthn for one of those:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;async&lt;/span&gt; &lt;span class="hl-kw"&gt;function&lt;/span&gt; &lt;span class="hl-fn"&gt;loginWebAuthnAccount&lt;/span&gt;(loginForm, conditional) {
    &lt;span class="hl-cmt"&gt;// ...&lt;/span&gt;
    &lt;span class="hl-kw"&gt;let&lt;/span&gt; allowCredentials &lt;span class="hl-op"&gt;=&lt;/span&gt; [];
    &lt;span class="hl-kw"&gt;if&lt;/span&gt; (&lt;span class="hl-op"&gt;!&lt;/span&gt;conditional) {
		&lt;span class="hl-kw"&gt;const&lt;/span&gt; email &lt;span class="hl-op"&gt;=&lt;/span&gt; loginForm[&lt;span class="hl-str"&gt;"user[email]"&lt;/span&gt;].&lt;span class="hl-prop"&gt;value&lt;/span&gt;;
		&lt;span class="hl-kw"&gt;const&lt;/span&gt; resp &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-kw"&gt;await&lt;/span&gt; &lt;span class="hl-fn"&gt;fetch&lt;/span&gt;(&lt;span class="hl-str"&gt;`/users/log_in/credentials?email=&lt;span class="hl-rst"&gt;&lt;span class="hl-psp"&gt;${&lt;/span&gt;email&lt;span class="hl-psp"&gt;}&lt;/span&gt;&lt;/span&gt;`&lt;/span&gt;);
		&lt;span class="hl-kw"&gt;const&lt;/span&gt; respJSON &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-kw"&gt;await&lt;/span&gt; resp.&lt;span class="hl-fn"&gt;json&lt;/span&gt;();
		allowCredentials &lt;span class="hl-op"&gt;=&lt;/span&gt; respJSON.&lt;span class="hl-fn"&gt;map&lt;/span&gt;((id) &lt;span class="hl-op"&gt;=&amp;gt;&lt;/span&gt; {
			&lt;span class="hl-kw"&gt;return&lt;/span&gt; {
				&lt;span class="hl-prop"&gt;type&lt;/span&gt;: &lt;span class="hl-str"&gt;"public-key"&lt;/span&gt;,
				&lt;span class="hl-prop"&gt;id&lt;/span&gt;: &lt;span class="hl-fn"&gt;base64ToArrayBuffer&lt;/span&gt;(id),
			};
		});
    }

    &lt;span class="hl-kw"&gt;const&lt;/span&gt; getOptions &lt;span class="hl-op"&gt;=&lt;/span&gt; {
		&lt;span class="hl-prop"&gt;mediation&lt;/span&gt;: conditional ? &lt;span class="hl-str"&gt;"conditional"&lt;/span&gt; : &lt;span class="hl-str"&gt;"optional"&lt;/span&gt;,
		&lt;span class="hl-prop"&gt;publicKey&lt;/span&gt;: {
			&lt;span class="hl-prop"&gt;challenge&lt;/span&gt;: &lt;span class="hl-fn"&gt;base64URLToArrayBuffer&lt;/span&gt;(challenge),
			&lt;span class="hl-prop"&gt;rpId&lt;/span&gt;: &lt;span class="hl-str"&gt;"localhost"&lt;/span&gt;,
			allowCredentials,
		}
    };
    &lt;span class="hl-cmt"&gt;// ...&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The “optional” value for the &lt;code&gt;mediation&lt;/code&gt; option means that the authenticator isn’t required to display UI, but will do so if its policies dictate that. The &lt;code&gt;allowCredentials&lt;/code&gt; array contains objects describing all of the credentials that we want to accept—specifically, their binary IDs as &lt;code&gt;ArrayBuffer&lt;/code&gt;s.&lt;/p&gt;
&lt;p&gt;We look up the user’s credentials so that the one we actually request from the authenticator matches the account that the user is trying to log in with. To handle this, we’ll also wire up an additional route on the backend that returns the base 64-encoded IDs of all the credentials belonging to the user with a given email.&lt;/p&gt;
&lt;aside&gt;
&lt;p&gt;This is a vector for user enumeration, which may or may not matter depending on your particular use case. If it does matter and you can’t do this, I would forego conditional UI entirely have the WebAuthn flow triggered by a plain button press or something.&lt;/p&gt;
&lt;/aside&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;defmodule&lt;/span&gt; &lt;span class="hl-mod"&gt;PhoenixPasskeysWeb.UserSessionController&lt;/span&gt; &lt;span class="hl-kw"&gt;do&lt;/span&gt;
  &lt;span class="hl-kw"&gt;def&lt;/span&gt; &lt;span class="hl-fn"&gt;credentials&lt;/span&gt;(conn, %{&lt;span class="hl-str"&gt;"email"&lt;/span&gt; &lt;span class="hl-op"&gt;=&amp;gt;&lt;/span&gt; email}) &lt;span class="hl-kw"&gt;do&lt;/span&gt;
    ids &lt;span class="hl-op"&gt;=&lt;/span&gt;
      &lt;span class="hl-mod"&gt;Accounts&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;get_credentials_by_email&lt;/span&gt;(email)
      &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-mod"&gt;Enum&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;map&lt;/span&gt;(&lt;span class="hl-kw"&gt;fn&lt;/span&gt; cred &lt;span class="hl-op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="hl-mod"&gt;Base&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;encode64&lt;/span&gt;(cred&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-prop"&gt;id&lt;/span&gt;) &lt;span class="hl-kw"&gt;end&lt;/span&gt;)

    &lt;span class="hl-fn"&gt;json&lt;/span&gt;(conn, ids)
  &lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;get_credentials_by_email&lt;/code&gt; function is quite simple. It just looks up a user by email, preloading any credentials they have and then returning them:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;defmodule&lt;/span&gt; &lt;span class="hl-mod"&gt;PhoenixPasskeys.Accounts&lt;/span&gt; &lt;span class="hl-kw"&gt;do&lt;/span&gt;
  &lt;span class="hl-kw"&gt;def&lt;/span&gt; &lt;span class="hl-fn"&gt;get_credentials_by_email&lt;/span&gt;(email) &lt;span class="hl-kw"&gt;do&lt;/span&gt;
    &lt;span class="hl-mod"&gt;Repo&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;one&lt;/span&gt;(&lt;span class="hl-fn"&gt;from&lt;/span&gt; u &lt;span class="hl-kw"&gt;in&lt;/span&gt; &lt;span class="hl-mod"&gt;User&lt;/span&gt;, &lt;span class="hl-str"&gt;where: &lt;/span&gt;u&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-prop"&gt;email&lt;/span&gt; &lt;span class="hl-op"&gt;==&lt;/span&gt; &lt;span class="hl-op"&gt;^&lt;/span&gt;email, &lt;span class="hl-str"&gt;preload: &lt;/span&gt;&lt;span class="hl-str"&gt;:credentials&lt;/span&gt;)
    &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-kw"&gt;case&lt;/span&gt; &lt;span class="hl-kw"&gt;do&lt;/span&gt;
      %{&lt;span class="hl-str"&gt;credentials: &lt;/span&gt;credentials} &lt;span class="hl-op"&gt;-&amp;gt;&lt;/span&gt; credentials
      &lt;span class="hl-const"&gt;nil&lt;/span&gt; &lt;span class="hl-op"&gt;-&amp;gt;&lt;/span&gt; []
    &lt;span class="hl-kw"&gt;end&lt;/span&gt;
  &lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Back in the JS, we can tweak the setup code to pass &lt;code&gt;true&lt;/code&gt; for the conditional parameter in the initial request and also register a submit handler on the login form that will invoke it with &lt;code&gt;false&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-builtin"&gt;document&lt;/span&gt;.&lt;span class="hl-fn"&gt;addEventListener&lt;/span&gt;(&lt;span class="hl-str"&gt;"DOMContentLoaded"&lt;/span&gt;, () &lt;span class="hl-op"&gt;=&amp;gt;&lt;/span&gt; {
    &lt;span class="hl-cmt"&gt;// ...&lt;/span&gt;
	&lt;span class="hl-kw"&gt;const&lt;/span&gt; loginForm &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-builtin"&gt;document&lt;/span&gt;.&lt;span class="hl-fn"&gt;getElementById&lt;/span&gt;(&lt;span class="hl-str"&gt;"login-form"&lt;/span&gt;);
	&lt;span class="hl-kw"&gt;if&lt;/span&gt; (loginForm) {
		&lt;span class="hl-fn"&gt;loginWebAuthnAccount&lt;/span&gt;(loginForm, &lt;span class="hl-const"&gt;true&lt;/span&gt;);
		loginForm.&lt;span class="hl-fn"&gt;addEventListener&lt;/span&gt;(&lt;span class="hl-str"&gt;"submit"&lt;/span&gt;, (event) &lt;span class="hl-op"&gt;=&amp;gt;&lt;/span&gt; {
			event.&lt;span class="hl-fn"&gt;preventDefault&lt;/span&gt;();
			&lt;span class="hl-fn"&gt;loginWebAuthnAccount&lt;/span&gt;(loginForm, &lt;span class="hl-const"&gt;false&lt;/span&gt;);
		});
	}
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And so we’ve handled the case where the user ignores the conditional UI and still types in their email to log in.&lt;/p&gt;
&lt;h2&gt;Passkey reset&lt;/h2&gt;
&lt;p&gt;A not-infrequent argument against passkeys is that if your phone falls into a lake, you lose access to all of your passkey-backed accounts. One could argue that this isn’t true because the definition of passkeys means that they’re backed up and thus protected from this sort of event (and indeed, earlier we only permitted registering with backed-up credentials). I think the argument isn’t particularly interesting, however, because you can still have a “Forgot my passkey” option that works just like it does now with passwords.&lt;/p&gt;
&lt;p&gt;This is less secure than a passkey implementation that has no “Forgot” option. But it’s no less secure than current password-based systems, and I think the UX/security tradeoff here falls on the UX side—people will, inevitably, lose access to their passkeys while retaining access to their email.&lt;/p&gt;
&lt;p&gt;Implementing isn’t too complicated, fortunately, since we can reuse much of the registration code. First, the JavaScript. The only change necessary is attaching the registration function to the reset form as well.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-builtin"&gt;document&lt;/span&gt;.&lt;span class="hl-fn"&gt;addEventListener&lt;/span&gt;(&lt;span class="hl-str"&gt;"DOMContentLoaded"&lt;/span&gt;, () &lt;span class="hl-op"&gt;=&amp;gt;&lt;/span&gt; {
    &lt;span class="hl-cmt"&gt;// ...&lt;/span&gt;
	&lt;span class="hl-kw"&gt;const&lt;/span&gt; resetForm &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-builtin"&gt;document&lt;/span&gt;.&lt;span class="hl-fn"&gt;getElementById&lt;/span&gt;(&lt;span class="hl-str"&gt;"reset-passkey-form"&lt;/span&gt;);
	&lt;span class="hl-kw"&gt;if&lt;/span&gt; (resetForm) {
		resetForm.&lt;span class="hl-fn"&gt;addEventListener&lt;/span&gt;(&lt;span class="hl-str"&gt;"submit"&lt;/span&gt;, (event) &lt;span class="hl-op"&gt;=&amp;gt;&lt;/span&gt; {
			event.&lt;span class="hl-fn"&gt;preventDefault&lt;/span&gt;();
			&lt;span class="hl-fn"&gt;registerWebAuthnAccount&lt;/span&gt;(resetForm);
		});
	}
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the HTML for the reset form, we we need to include the email in the same form field as the registration form (and also add an ID to the &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; element):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&amp;lt;&lt;span class="hl-tag"&gt;input&lt;/span&gt; &lt;span class="hl-attr"&gt;type&lt;/span&gt;="&lt;span class="hl-str"&gt;hidden&lt;/span&gt;" &lt;span class="hl-attr"&gt;name&lt;/span&gt;="&lt;span class="hl-str"&gt;user[email]&lt;/span&gt;" &lt;span class="hl-attr"&gt;value&lt;/span&gt;=&lt;span class="hl-str"&gt;{@user.email}&lt;/span&gt; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the function for the reset route (which is changed to POST from PUT, to match the signup route), we take the credential ID and public key and use them to update the user’s credentials:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;defmodule&lt;/span&gt; &lt;span class="hl-mod"&gt;PhoenixPasskeysWeb.UserResetPasswordController&lt;/span&gt; &lt;span class="hl-kw"&gt;do&lt;/span&gt;
  &lt;span class="hl-kw"&gt;def&lt;/span&gt; &lt;span class="hl-fn"&gt;update&lt;/span&gt;(conn, %{
        &lt;span class="hl-str"&gt;"credential_id"&lt;/span&gt; &lt;span class="hl-op"&gt;=&amp;gt;&lt;/span&gt; credential_id,
        &lt;span class="hl-str"&gt;"public_key_spki"&lt;/span&gt; &lt;span class="hl-op"&gt;=&amp;gt;&lt;/span&gt; public_key_spki
      }) &lt;span class="hl-kw"&gt;do&lt;/span&gt;
    credential_id &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-mod"&gt;Base&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;decode64!&lt;/span&gt;(credential_id)
    public_key_spki &lt;span class="hl-op"&gt;=&lt;/span&gt; &lt;span class="hl-mod"&gt;Base&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;decode64!&lt;/span&gt;(public_key_spki)
    &lt;span class="hl-kw"&gt;case&lt;/span&gt; &lt;span class="hl-mod"&gt;Accounts&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;reset_user_credentials&lt;/span&gt;(conn&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-prop"&gt;assigns&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-prop"&gt;user&lt;/span&gt;, credential_id, public_key_spki) &lt;span class="hl-kw"&gt;do&lt;/span&gt;
      &lt;span class="hl-str"&gt;:ok&lt;/span&gt; &lt;span class="hl-op"&gt;-&amp;gt;&lt;/span&gt;
        conn
        &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-fn"&gt;put_flash&lt;/span&gt;(&lt;span class="hl-str"&gt;:info&lt;/span&gt;, &lt;span class="hl-str"&gt;"Passkey reset successfully."&lt;/span&gt;)
        &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-mod"&gt;UserAuth&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;log_in_user_without_redirect&lt;/span&gt;(conn&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-prop"&gt;assigns&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-prop"&gt;user&lt;/span&gt;)
        &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-fn"&gt;json&lt;/span&gt;(%{&lt;span class="hl-str"&gt;status: &lt;/span&gt;&lt;span class="hl-str"&gt;:ok&lt;/span&gt;})

      {&lt;span class="hl-str"&gt;:error&lt;/span&gt;, &lt;span class="hl-cmt"&gt;_&lt;/span&gt;} &lt;span class="hl-op"&gt;-&amp;gt;&lt;/span&gt;
        conn
        &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-fn"&gt;put_flash&lt;/span&gt;(&lt;span class="hl-str"&gt;:error&lt;/span&gt;, &lt;span class="hl-str"&gt;"Error resetting passkey"&lt;/span&gt;)
        &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-fn"&gt;json&lt;/span&gt;(%{&lt;span class="hl-str"&gt;status: &lt;/span&gt;&lt;span class="hl-str"&gt;:error&lt;/span&gt;})
    &lt;span class="hl-kw"&gt;end&lt;/span&gt;
  &lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After updating, we log the user in if applicable and return JSON with the appropriate status.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;reset_user_credentials&lt;/code&gt; function works very similarly to the original reset password function that was part of the template: it deletes all the user’s existing sessions, and then removes their existing credentials and creates a new one:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;defmodule&lt;/span&gt; &lt;span class="hl-mod"&gt;PhoenixPasskeys.Accounts&lt;/span&gt; &lt;span class="hl-kw"&gt;do&lt;/span&gt;
  &lt;span class="hl-kw"&gt;def&lt;/span&gt; &lt;span class="hl-fn"&gt;reset_user_credentials&lt;/span&gt;(user, credential_id, public_key_spki) &lt;span class="hl-kw"&gt;do&lt;/span&gt;
    &lt;span class="hl-mod"&gt;Ecto.Multi&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;new&lt;/span&gt;()
    &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-mod"&gt;Ecto.Multi&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;delete_all&lt;/span&gt;(
      &lt;span class="hl-str"&gt;:old_credentials&lt;/span&gt;,
      &lt;span class="hl-fn"&gt;from&lt;/span&gt;(a &lt;span class="hl-kw"&gt;in&lt;/span&gt; &lt;span class="hl-mod"&gt;UserCredential&lt;/span&gt;, &lt;span class="hl-str"&gt;where: &lt;/span&gt;a&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-prop"&gt;user_id&lt;/span&gt; &lt;span class="hl-op"&gt;==&lt;/span&gt; &lt;span class="hl-op"&gt;^&lt;/span&gt;user&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-prop"&gt;id&lt;/span&gt;)
    )
    &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-mod"&gt;Ecto.Multi&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;insert&lt;/span&gt;(
      &lt;span class="hl-str"&gt;:new_credential&lt;/span&gt;,
      &lt;span class="hl-mod"&gt;UserCredential&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;changeset&lt;/span&gt;(%&lt;span class="hl-mod"&gt;UserCredential&lt;/span&gt;{}, %{
        &lt;span class="hl-str"&gt;id: &lt;/span&gt;credential_id,
        &lt;span class="hl-str"&gt;public_key_spki: &lt;/span&gt;public_key_spki,
        &lt;span class="hl-str"&gt;user_id: &lt;/span&gt;user&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-prop"&gt;id&lt;/span&gt;
      })
    )
    &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-mod"&gt;Ecto.Multi&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;delete_all&lt;/span&gt;(&lt;span class="hl-str"&gt;:tokens&lt;/span&gt;, &lt;span class="hl-mod"&gt;UserToken&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;user_and_contexts_query&lt;/span&gt;(user, &lt;span class="hl-str"&gt;:all&lt;/span&gt;))
    &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-mod"&gt;Repo&lt;/span&gt;&lt;span class="hl-op"&gt;.&lt;/span&gt;&lt;span class="hl-fn"&gt;transaction&lt;/span&gt;()
    &lt;span class="hl-op"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="hl-kw"&gt;case&lt;/span&gt; &lt;span class="hl-kw"&gt;do&lt;/span&gt;
      {&lt;span class="hl-str"&gt;:ok&lt;/span&gt;, &lt;span class="hl-cmt"&gt;_&lt;/span&gt;} &lt;span class="hl-op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="hl-str"&gt;:ok&lt;/span&gt;
      {&lt;span class="hl-str"&gt;:error&lt;/span&gt;, &lt;span class="hl-cmt"&gt;_&lt;/span&gt;, changeset, &lt;span class="hl-cmt"&gt;_&lt;/span&gt;} &lt;span class="hl-op"&gt;-&amp;gt;&lt;/span&gt; {&lt;span class="hl-str"&gt;:error&lt;/span&gt;, changeset}
    &lt;span class="hl-kw"&gt;end&lt;/span&gt;
  &lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;span class="hl-kw"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s worth noting that this does have slightly weaker security properties than the &lt;code&gt;phx.gen.auth&lt;/code&gt; reset password implementation. With that approach, leaking a password reset token does not necessarily result in an account takeover, since whoever obtained the leaked token may not know the target user’s email address. Since the auth template forces the user to re-login after a reset, this prevents someone without the email address from gaining access even if they change the password.&lt;/p&gt;
&lt;p&gt;But since logging in with a passkey is functionally a single factor, resetting it means gaining access to the account. So, a leaked reset token gives the bearer control over the account. This is an argument against having a reset option, but whether this is a concern in practice depends on your specific circumstances.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;As noted, this is not a complete implementation. There are a handful of places where I’ve left things unfinished since this isn’t meant to be production-level code. There are also a few places where there are security decisions that need to be made on a more contextual basis, that I’ve tried to note. And, of course, you wouldn’t really want to only permit signing in with passkeys and wholesale drop the passwords column from your database.&lt;/p&gt;
&lt;p&gt;Nonetheless, I hope this has been a helpful look at how to implement passkeys in an Elixir/Phoenix application. You can find the complete repo &lt;a href="https://git.shadowfacts.net/shadowfacts/phoenix_passkeys"&gt;here&lt;/a&gt;, and it may also be useful to look at the &lt;a href="https://git.shadowfacts.net/shadowfacts/phoenix_passkeys/commit/ce4f485dbc4e528bdd13a57b0d379012dd893338"&gt;specific commit&lt;/a&gt; where passkey support was added.&lt;/p&gt;</description><author>Shadowfacts</author><pubDate>Thu, 19 Oct 2023 18:00:42 GMT</pubDate><guid isPermaLink="true">https://shadowfacts.net/2023/phoenix-passkeys/</guid></item><item><title>Networking as an introvert CTO</title><link>https://vadimkravcenko.com/shorts/networking-introvert-cto/</link><description>&lt;p&gt;Question:&lt;/p&gt;
&lt;/p&gt;
&lt;p&gt; Answer:&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://vadimkravcenko.com/shorts/networking-introvert-cto/"&gt;Networking as an introvert CTO&lt;/a&gt; appeared first on &lt;a href="https://vadimkravcenko.com"&gt;Vadim Kravcenko&lt;/a&gt;.&lt;/p&gt;</description><author>Vadim Kravcenko</author><pubDate>Thu, 19 Oct 2023 17:52:15 GMT</pubDate><guid isPermaLink="true">https://vadimkravcenko.com/shorts/networking-introvert-cto/</guid></item><item><title>Advice on early-stage fundraising</title><link>https://philippzentner.com/advice-on-early-stage-fundraising.html</link><description>&lt;img alt="" src="https://philippzentner.com/media/posts/58/_DSC7446-copy-2.jpeg" /&gt;
                    I rarely find time to write (as you can tell), but over the past few months, I've been inundated with requests for advice on fundraising. The most common request is for direct introductions to investors, but many other frequent questions came up. I've summarized some&amp;hellip;</description><author>Philipp Zentner</author><pubDate>Thu, 19 Oct 2023 13:19:27 GMT</pubDate><guid isPermaLink="true">https://philippzentner.com/advice-on-early-stage-fundraising.html</guid></item><item><title>What is a vector database?</title><link>https://zackproser.com/videos/what-is-a-vector-database</link><description>Vector databases explained, featuring clowns, embeddings, neural networks, feature extraction, semantic search and Retrieval Augmented Generation (RAG).</description><author>Zachary Proser</author><pubDate>Thu, 19 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/videos/what-is-a-vector-database</guid></item><item><title>Deploying Nomad on NixOS</title><link>https://heitorpb.github.io/bla/nomad-on-nixos/</link><description>How to get Nomad running on NixOS for local development, with Podman and Docker task drivers.</description><author>Heitor's log</author><pubDate>Thu, 19 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://heitorpb.github.io/bla/nomad-on-nixos/</guid></item><item><title>The List</title><link>https://www.marginalia.nu/log/91-the-list/</link><description>&lt;p&gt;As a general observation, I tend to be more productive when I know what
to do next at any given moment.&lt;/p&gt;
&lt;p&gt;There are days when I&amp;rsquo;ve seemingly gotten a &amp;ldquo;week&amp;rdquo; of work done on an afternoon,
those are the days when what I needed to do was very clear, and I basically just
had a list of items to tick off one by one.&lt;/p&gt;
&lt;p&gt;There have admittedly also those ignoble weeks weeks when I&amp;rsquo;ve gotten an
afternoon&amp;rsquo;s work done, mostly they are weeks when it&amp;rsquo;s not been at all clear
what to do next.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Thu, 19 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/91-the-list/</guid></item><item><title>Riff: A "mycelium-clj" for the Clojure ecosystem?</title><link>https://www.evalapply.org/posts/mycelium-clj/index.html</link><description>In a world of concrete objects, steel frameworks bring sense and order. In a forest of composable tools, libraries and open-ended schemas, it would be the mycelia. A frustrated yet optimistic man muses "Might such a thing come to be?".</description><author>Aditya Athalye's Eval/Apply Blog</author><pubDate>Thu, 19 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.evalapply.org/posts/mycelium-clj/index.html</guid></item><item><title>Scream-Driven-Development</title><link>https://backendhance.com/blog/2023/scream-driven-development/</link><description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;p&gt;Das kennst du bestimmt. Du willst einen &lt;strong&gt;Breaking-Change&lt;/strong&gt; in deinem System machen. Vielleicht willst du eine API ändern. Oder du möchtest deine Environments umstellen. Oder du willst einen Service abschalten, weil er nicht mehr benötigt wird.&lt;/p&gt;
&lt;p&gt;Immer wenn ich vor so einer Entscheidung stehe frage ich mich welche Konsequenzen sie haben wird. Wird sie andere Stakeholder beeinflussen? Meistens lautet die Antwort, ja.&lt;/p&gt;
&lt;p&gt;So auch im aktuellen Fall. Ich möchte häufiger Deployments in einem Environment durchführen. Sie wird genutzt, um den vorstehenden Release zu testen. Und, um den Feedbackloop kurz zu halten, möchte ich immer den aktuellen Entwicklungsstand auf der Plattform haben.&lt;/p&gt;</description><author>Backendhance</author><pubDate>Thu, 19 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/scream-driven-development/</guid></item><item><title>Living with Sudden Adult Death Syndrome (SADS)</title><link>https://taylor.town/sads</link><description>&lt;ul&gt;
&lt;li&gt;&lt;a href="#1"&gt;What is SADS?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#2"&gt;Who is at risk for SADS?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#3"&gt;How is SADS diagnosed?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#4"&gt;Can SADS be prevented?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#5"&gt;How do you prepare for SADS?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="1"&gt;What is SADS?&lt;/h2&gt;
&lt;p&gt;Sudden Adult Death Syndrome (SADS) is an unexpected death of an adult over 1
year(s) of age. Most SADS events are followed by autopsies, investigations, and
funerals. SADS is sometimes called "the long sleep" because of its permanence
and characteristic loss of consciousness.&lt;/p&gt;
&lt;h2 id="2"&gt;Who is at risk for SADS?&lt;/h2&gt;
&lt;p&gt;Over 55 million people die of SADS each year. That's more than heart disease,
old age, and cancer combined.&lt;/p&gt;
&lt;p&gt;The following factors may increase your risk of SADS:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a strict vegetarian diet&lt;/li&gt;
&lt;li&gt;attending work/school&lt;/li&gt;
&lt;li&gt;exercising religious/secular beliefs&lt;/li&gt;
&lt;li&gt;relavites with SADS&lt;/li&gt;
&lt;li&gt;having an astrological sign&lt;/li&gt;
&lt;li&gt;eating meat&lt;/li&gt;
&lt;li&gt;being born on a solar year&lt;/li&gt;
&lt;li&gt;eating after midnight&lt;/li&gt;
&lt;li&gt;birthdays&lt;/li&gt;
&lt;li&gt;the placebo effect&lt;/li&gt;
&lt;li&gt;vivid dreams&lt;/li&gt;
&lt;li&gt;having 10+ chromosomes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you exhibit more than three signs listed above, please consider drafting an
end-of-life plan as soon as possible.&lt;/p&gt;
&lt;h2 id="3"&gt;How is SADS diagnosed?&lt;/h2&gt;
&lt;p&gt;Medical experts use a variety of tests to determine whether a corpse has
experienced SADS during its lifetime.&lt;/p&gt;
&lt;p&gt;Common symptoms of SADS include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;loss of feeling in limbs, head, and torso&lt;/li&gt;
&lt;li&gt;visible skeleton&lt;/li&gt;
&lt;li&gt;merging with the void&lt;/li&gt;
&lt;li&gt;foul odor&lt;/li&gt;
&lt;li&gt;indifference to everything&lt;/li&gt;
&lt;li&gt;lack of movement&lt;/li&gt;
&lt;li&gt;slowly being forgotten&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="4"&gt;Can SADS be prevented?&lt;/h2&gt;
&lt;p&gt;Contrary to popular belief, anybody can avoid SADS with careful planning!
Prevent SADS by (1) ending your life at a predetermined date/time, (2)
perpetually expecting your bitter end, or (3) collecting all seven
&lt;a href="https://www.youtube.com/watch?v=fzTItNHFC94"&gt;chaos emeralds&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="5"&gt;How do you prepare for SADS?&lt;/h2&gt;
&lt;p&gt;Most people choose to accept (or ignore) life with SADS.&lt;/p&gt;
&lt;p&gt;To better prepare yourself and your loved ones for unexpected SADS events, try
some accepted best practices:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;go with the flow&lt;/li&gt;
&lt;li&gt;draft an uncontentious end-of-life plan in your early twenties&lt;/li&gt;
&lt;li&gt;keep no secrets; harbor no grudges; leave nothing unsaid&lt;/li&gt;
&lt;li&gt;learn to dance&lt;/li&gt;
&lt;li&gt;don't become a career&lt;/li&gt;
&lt;li&gt;observe; curate attention&lt;/li&gt;
&lt;li&gt;try your hardest, but don't try too hard&lt;/li&gt;
&lt;li&gt;ask for help&lt;/li&gt;
&lt;li&gt;be useful&lt;/li&gt;
&lt;li&gt;live with chaos&lt;/li&gt;
&lt;li&gt;update your end-of-life plan after every major life event&lt;/li&gt;
&lt;li&gt;play games without losing yourself in them&lt;/li&gt;
&lt;li&gt;take responsibility for choices and accidents; don't foist blame&lt;/li&gt;
&lt;li&gt;ignore extrinsic motivations&lt;/li&gt;
&lt;li&gt;learn how to say sorry and ask for forgiveness&lt;/li&gt;
&lt;li&gt;purge possessions -- you can't &lt;a href="https://taylor.town/to-your-grave"&gt;take everything to your grave&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;listen wholeheartedly to people; never interrupt&lt;/li&gt;
&lt;li&gt;accept pain; minimize suffering&lt;/li&gt;
&lt;li&gt;unravel your fears&lt;/li&gt;
&lt;li&gt;befriend Nature&lt;/li&gt;
&lt;li&gt;spend time wisely&lt;/li&gt;
&lt;/ul&gt;</description><author>taylor.town</author><pubDate>Thu, 19 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/sads</guid></item><item><title>WASM Seam Carving</title><link>https://trevo.rs/wasm-seam-carving</link><description>Shrinking images with WASM</description><author>Trevor Stenson's Blog</author><pubDate>Thu, 19 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://trevo.rs/wasm-seam-carving</guid></item><item><title>io_uring basics: Writing a file to disk</title><link>http://notes.eatonphil.com/2023-10-19-write-file-to-disk-with-io_uring.html</link><description>&lt;p&gt;King and I &lt;a href="https://tigerbeetle.com/blog/a-friendly-abstraction-over-iouring-and-kqueue/"&gt;wrote a blog
post&lt;/a&gt;
about building an event-driven cross-platform IO library that used
io_uring on Linux. We sketched out how it works at a high level but I
hadn't yet internalized how you actually code with io_uring. So I
strapped myself down this week and wrote &lt;a href="https://github.com/eatonphil/io-playground"&gt;some
benchmarks&lt;/a&gt; to build my
intuition about io_uring and other IO models.&lt;/p&gt;
&lt;p&gt;I started with implementations in Go and ported them to Zig to make
sure I had done the Go versions decently. And I got some help from
King and other internetters to find some inefficiencies in my code.&lt;/p&gt;
&lt;p&gt;This post will walk through my process, getting increasingly efficient
(and a little increasingly complex) ways to write an entire file to
disk with io_uring, from Go and Zig.&lt;/p&gt;
&lt;p&gt;Notably, we're not going to &lt;code&gt;fsync()&lt;/code&gt; and we're not going to use
&lt;code&gt;O_DIRECT&lt;/code&gt;. So we won't be testing the entire IO pipeline from
userland to disk hardware but just how fast IO gets to the kernel. The
focus of this post is more on IO methods and using io_uring, not
absolute numbers.&lt;/p&gt;
&lt;p&gt;All code for this post is &lt;a href="https://github.com/eatonphil/io_uring-basics-writing-file"&gt;available on
GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p class="note"&gt;
  This code is going to indirectly show some differences in timing
  between Go and Zig. I could care less about benchmarketing. And I
  hope something about Zig vs Go is not what you take away from this
  post either.
  &lt;br /&gt;
  &lt;br /&gt;
  The goal is to build an intuition and be generally
  correct. Observing the same relative behavior between
  implementations across two languages helps me gain confidence what
  I'm doing is correct.
&lt;/p&gt;&lt;h3 id="io_uring"&gt;io_uring&lt;/h3&gt;&lt;p&gt;With normal blocking syscalls you just call &lt;code&gt;read()&lt;/code&gt; or &lt;code&gt;write()&lt;/code&gt; and
wait for the results. io_uring is one of Linux's more powerful
&lt;em&gt;asynchronous&lt;/em&gt; IO offerings. Unlike epoll, you can use io_uring with
both files and network connections. And unlike epoll you can even have
the syscall run in the kernel.&lt;/p&gt;
&lt;p&gt;To interact with io_uring, you register a submission queue for syscalls
and their arguments. And you register a completion queue for syscall
results.&lt;/p&gt;
&lt;p&gt;You can batch many syscalls in one single call to io_uring,
effectively turning up to N (4096 at most) syscalls into just one
syscall. The kernel still does all the work of the N syscalls but you
avoid some overhead.&lt;/p&gt;
&lt;p&gt;As you check the completion queue and handle completed submissions,
the submission queue is also freed all or somewhat, and you can now
add more submissions.&lt;/p&gt;
&lt;p&gt;For a more complete understanding, check out the kernel document
&lt;a href="https://kernel.dk/io_uring.pdf"&gt;Efficient IO with io_uring&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="io_uring-vs-liburing"&gt;io_uring vs liburing&lt;/h3&gt;&lt;p&gt;io_uring is a complex, low-level interface. Shuveb Hussain has &lt;a href="https://unixism.net/2020/04/io-uring-by-example-part-1-introduction/"&gt;an
excellent
series&lt;/a&gt;
on programming io_uring. But that was too low-level for me as I was
trying to figure out how to just get something working.&lt;/p&gt;
&lt;p&gt;Instead, most people use &lt;a href="https://github.com/axboe/liburing"&gt;liburing&lt;/a&gt;
or a ported version of it like &lt;a href="https://github.com/ziglang/zig/blob/master/lib/std/os/linux/io_uring.zig"&gt;the Zig standard library's
io_uring.zig&lt;/a&gt;
or &lt;a href="https://github.com/Iceber/iouring-go"&gt;Iceber's iouring-go&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;io_uring started clicking for me when I tried out the iouring-go
library. So we'll start there.&lt;/p&gt;
&lt;h3 id="boilerplate"&gt;Boilerplate&lt;/h3&gt;&lt;p&gt;First off, let's set up some boilerplate for the Go and Zig code.&lt;/p&gt;
&lt;p&gt;In main.go add:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;bytes&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;fmt&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;os&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;time&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;assert&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;readNBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;%s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OpenFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;out.bin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;O_RDWR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;O_CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;O_TRUNC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mo"&gt;0755&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;t1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;Sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;Seconds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;,%f,%f\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;readNBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;out.bin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And in main.zig add:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;std&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OUT_FILE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;out.bin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;readNBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Allocator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;openFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;written&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nwritten&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;@memcpy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;written&lt;/span&gt;&lt;span class="p"&gt;..],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="n"&gt;nwritten&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;written&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nwritten&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Benchmark&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Allocator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Allocator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;Benchmark&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getStdOut&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{s}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;createFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OUT_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;truncate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@floatFromInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ns_per_s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getStdOut&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;,{d},{d}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@floatFromInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unreachable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;readNBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OUT_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unreachable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="keep-it-simple:-write()"&gt;Keep it simple: write()&lt;/h3&gt;&lt;p&gt;Now let's add the naive version of writing bytes to disk: calling
&lt;code&gt;write()&lt;/code&gt; repeatedly until all data has been written to disk.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;main.go&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;104857600&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 100MiB&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;readNBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/dev/random&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RUNS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RUNS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;blocking&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And in &lt;code&gt;main.zig&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kr"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;page_allocator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SIZE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;104857600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 100MiB&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;readNBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/dev/random&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SIZE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RUNS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RUNS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;blocking&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's build and run these programs and store the results to CSV we
can analyze with DuckDB.&lt;/p&gt;
&lt;p&gt;Go first:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;go&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;main.go&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;gomain
$&lt;span class="w"&gt; &lt;/span&gt;./gomain&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;go.csv
$&lt;span class="w"&gt; &lt;/span&gt;duckdb&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;select column0 as method, avg(cast(column1 as double)) || 's' avg_time, format_bytes(avg(column2::double)::bigint) || '/s' as avg_throughput from 'go.csv' group by column0 order by avg(cast(column1 as double)) asc&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;method&lt;/th&gt;
&lt;th&gt;avg_time&lt;/th&gt;
&lt;th&gt;avg_throughput&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;blocking&lt;/td&gt;
&lt;td&gt;0.07251540000000001s&lt;/td&gt;
&lt;td&gt;1.4GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;And Zig:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;zig&lt;span class="w"&gt; &lt;/span&gt;build-exe&lt;span class="w"&gt; &lt;/span&gt;main.zig
$&lt;span class="w"&gt; &lt;/span&gt;./main&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;zig.csv
$&lt;span class="w"&gt; &lt;/span&gt;duckdb&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;select column0 as method, avg(cast(column1 as double)) || 's' avg_time, format_bytes(avg(column2::double)::bigint) || '/s' as avg_throughput from 'zig.csv' group by column0 order by avg(cast(column1 as double)) asc&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;method&lt;/th&gt;
&lt;th&gt;avg_time&lt;/th&gt;
&lt;th&gt;avg_throughput&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;blocking&lt;/td&gt;
&lt;td&gt;0.0656907669s&lt;/td&gt;
&lt;td&gt;1.5GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Alright, we've got a baseline now and both language implementations
are in the same ballpark.&lt;/p&gt;
&lt;p&gt;Let's add a simple io_uring version!&lt;/p&gt;
&lt;h3 id="io_uring,-1-entry,-go"&gt;io_uring, 1 entry, Go&lt;/h3&gt;&lt;p&gt;The &lt;a href="https://github.com/Iceber/iouring-go#quickstart"&gt;iouring-go&lt;/a&gt;
library has really excellent documentation for getting started.&lt;/p&gt;
&lt;p&gt;To keep it simple, we'll use io_uring with only 1 entry. Add the
following to &lt;code&gt;func main()&lt;/code&gt; after the existing &lt;code&gt;benchmark()&lt;/code&gt; call in
&lt;code&gt;main.go&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;io_uring&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;iour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iouring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iour&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;prepRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iouring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fd&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iour&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubmitRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prepRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReturnInt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note that &lt;code&gt;benchmark&lt;/code&gt; takes care of &lt;code&gt;f.Seek(0)&lt;/code&gt; before each run. And
it also validates that the file contents are equivalent to the input
&lt;code&gt;data&lt;/code&gt;. So it validates the benchmark for correctness.&lt;/p&gt;
&lt;p&gt;Alright, let's run this new Go implementation with io_uring!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;go&lt;span class="w"&gt; &lt;/span&gt;mod&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;gomain
$&lt;span class="w"&gt; &lt;/span&gt;go&lt;span class="w"&gt; &lt;/span&gt;mod&lt;span class="w"&gt; &lt;/span&gt;tidy
$&lt;span class="w"&gt; &lt;/span&gt;go&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;main.go&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;gomain
$&lt;span class="w"&gt; &lt;/span&gt;./gomain&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;go.csv
$&lt;span class="w"&gt; &lt;/span&gt;duckdb&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;select column0 as method, avg(cast(column1 as double)) || 's' avg_time, format_bytes(avg(column2::double)::bigint) || '/s' as avg_throughput from 'go.csv' group by column0 order by avg(cast(column1 as double)) asc&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;method&lt;/th&gt;
&lt;th&gt;avg_time&lt;/th&gt;
&lt;th&gt;avg_throughput&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;blocking&lt;/td&gt;
&lt;td&gt;0.0811486s&lt;/td&gt;
&lt;td&gt;1.3GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;io_uring&lt;/td&gt;
&lt;td&gt;0.5083049999999999s&lt;/td&gt;
&lt;td&gt;213.2MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Well that looks terrible.&lt;/p&gt;
&lt;p&gt;Let's port it to Zig to see if we notice the same behavior there.&lt;/p&gt;
&lt;h3 id="io_uring,-1-entry,-zig"&gt;io_uring, 1 entry, Zig&lt;/h3&gt;&lt;p&gt;There isn't an official Zig tutorial on io_uring I'm aware of. But
&lt;a href="https://github.com/ziglang/zig/blob/master/lib/std/os/linux/io_uring.zig"&gt;io_uring.zig&lt;/a&gt;
is easy enough to browse through. And there are tests in that file
that also show how to use it.&lt;/p&gt;
&lt;p&gt;And now that we've explored a bit in Go the basic gist should be
similar:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;initialize io_uring&lt;/li&gt;
&lt;li&gt;submit an entry&lt;/li&gt;
&lt;li&gt;wait for it to finish&lt;/li&gt;
&lt;li&gt;move on&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Add the following to &lt;code&gt;fn main()&lt;/code&gt; after the existing benchmark block in
&lt;code&gt;main.zig&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;iouring&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linux&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IO_Uring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deinit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;submitted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;submit_and_wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;submitted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy_cqe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SUCCESS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@intCast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now build and run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;zig&lt;span class="w"&gt; &lt;/span&gt;build-exe&lt;span class="w"&gt; &lt;/span&gt;main.zig
$&lt;span class="w"&gt; &lt;/span&gt;./main&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;zig.csv
$&lt;span class="w"&gt; &lt;/span&gt;duckdb&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;select column0 as method, avg(cast(column1 as double)) || 's' avg_time, format_bytes(avg(column2::double)::bigint) || '/s' as avg_throughput from 'zig.csv' group by column0 order by avg(cast(column1 as double)) asc&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;method&lt;/th&gt;
&lt;th&gt;avg_time&lt;/th&gt;
&lt;th&gt;avg_throughput&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;blocking&lt;/td&gt;
&lt;td&gt;0.06650093630000001s&lt;/td&gt;
&lt;td&gt;1.5GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;io_uring&lt;/td&gt;
&lt;td&gt;0.17542890139999998s&lt;/td&gt;
&lt;td&gt;597.7MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Well it's similarly pretty bad. But our implementation ignores one
major aspect of io_uring: batching requests.&lt;/p&gt;
&lt;p&gt;Let's do some refactoring!&lt;/p&gt;
&lt;h3 id="io_uring,-n-entries,-go"&gt;io_uring, N entries, Go&lt;/h3&gt;&lt;p&gt;To support submitting N entries, we're going to have an inner loop
running up to N that fills up N entries to io_uring.&lt;/p&gt;
&lt;p&gt;Then we'll wait for the N submissions to complete and check their
results.&lt;/p&gt;
&lt;p&gt;We'll keep going until we write the entire file.&lt;/p&gt;
&lt;p&gt;All of this can stay inside the loop in &lt;code&gt;main&lt;/code&gt;, I'm just dropping
preceding whitespace for nicer formatting here:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nx"&gt;benchmarkIOUringNEntries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nEntries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;io_uring_%d_entries&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nEntries&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;iour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;iouring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;uint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nEntries&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;iour&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="nx"&gt;iouring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PrepRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nEntries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nEntries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;submittedEntries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nEntries&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BUFFER_SIZE&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;submittedEntries&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;iouring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Pwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Fd&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;submittedEntries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;iour&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SubmitRequests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="nx"&gt;submittedEntries&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ErrResults&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReturnInt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;benchmarkIOUringNEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;benchmarkIOUringNEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There are some specific things in there to notice.&lt;/p&gt;
&lt;p&gt;First, toward the end of the file we may not have &lt;code&gt;N&lt;/code&gt; entries to
submit. We may have &lt;code&gt;1&lt;/code&gt; or even &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If we have &lt;code&gt;0&lt;/code&gt; to submit, we need to not even submit anything
otherwise the Go library hangs. Similarly, if we don't slice
&lt;code&gt;requests&lt;/code&gt; to &lt;code&gt;requests[:submittedEntries]&lt;/code&gt;, the Go library will
segfault if &lt;code&gt;submittedEntries &amp;lt; N&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Other than that, let's build and run this!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;go&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;gomain
$&lt;span class="w"&gt; &lt;/span&gt;./gomain&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;go.csv
$&lt;span class="w"&gt; &lt;/span&gt;duckdb&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;select column0 as method, avg(cast(column1 as double)) || 's' avg_time, format_bytes(avg(column2::double)::bigint) || '/s' as avg_throughput from 'go.csv' group by column0 order by avg(cast(column1 as double)) asc&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;method&lt;/th&gt;
&lt;th&gt;avg_time&lt;/th&gt;
&lt;th&gt;avg_throughput&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;blocking&lt;/td&gt;
&lt;td&gt;0.0740368s&lt;/td&gt;
&lt;td&gt;1.4GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;io_uring_128_entries&lt;/td&gt;
&lt;td&gt;0.127519s&lt;/td&gt;
&lt;td&gt;836.6MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;io_uring_1_entries&lt;/td&gt;
&lt;td&gt;0.46831579999999995s&lt;/td&gt;
&lt;td&gt;226.9MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Now we're getting somewhere! Still half the throughput but a 4x
improvement from using only a single entry.&lt;/p&gt;
&lt;p&gt;Let's port the N entry code to Zig.&lt;/p&gt;
&lt;h3 id="io_uring,-n-entries,-zig"&gt;io_uring, N entries, Zig&lt;/h3&gt;&lt;p&gt;Unlike Go we can't do closures, so we'll have to make
&lt;code&gt;benchmarkIOUringNEntries&lt;/code&gt; a top-level function and keep the calls to
it in the loop in &lt;code&gt;main&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kr"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;page_allocator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SIZE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;104857600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 100MiB&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;readNBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/dev/random&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SIZE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RUNS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RUNS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;blocking&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;benchmarkIOUringNEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;benchmarkIOUringNEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And for the implementation itself, the only two big differences from
the first version are that we'll bulk-read completion events (&lt;code&gt;cqe&lt;/code&gt;s)
and that we'll create and wait for many submissions at once.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;benchmarkIOUringNEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Allocator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;nEntries&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allocPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;iouring_{}_entries&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;&lt;span class="n"&gt;nEntries&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linux&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IO_Uring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nEntries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deinit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cqes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linux&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;io_uring_cqe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nEntries&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nEntries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;submittedEntries&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nEntries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;submittedEntries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;submitted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;submit_and_wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;submittedEntries&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;submitted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;submittedEntries&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;waited&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy_cqes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="n"&gt;submitted&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;submitted&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;waited&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;submitted&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="n"&gt;submitted&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|*&lt;/span&gt;&lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SUCCESS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@intCast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's build and run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;zig&lt;span class="w"&gt; &lt;/span&gt;build-exe&lt;span class="w"&gt; &lt;/span&gt;main.zig
$&lt;span class="w"&gt; &lt;/span&gt;./main&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;zig.csv
$&lt;span class="w"&gt; &lt;/span&gt;duckdb&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;select column0 as method, avg(cast(column1 as double)) || 's' avg_time, format_bytes(avg(column2::double)::bigint) || '/s' as avg_throughput from 'zig.csv' group by column0 order by avg(cast(column1 as double)) asc&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;method&lt;/th&gt;
&lt;th&gt;avg_time&lt;/th&gt;
&lt;th&gt;avg_throughput&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;blocking&lt;/td&gt;
&lt;td&gt;0.0674331114s&lt;/td&gt;
&lt;td&gt;1.5GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iouring_128_entries&lt;/td&gt;
&lt;td&gt;0.06773539590000001s&lt;/td&gt;
&lt;td&gt;1.5GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iouring_1_entries&lt;/td&gt;
&lt;td&gt;0.1855542556s&lt;/td&gt;
&lt;td&gt;569.9MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Huh, that's surprising! We caught up to blocking writes with io_uring
in Zig, but not in Go, even though we made good progress in Go.&lt;/p&gt;
&lt;h3 id="ring-buffers"&gt;Ring buffers&lt;/h3&gt;&lt;p&gt;But we can do a bit better. We're doing batching, but the API is
called "io_uring" not "io_batch". We're not even making use of the
ring buffer behavior io_uring gives us!&lt;/p&gt;
&lt;p&gt;We are waiting for all submitted results complete. But there's no
reason to do that. Instead we should submit as much as we can. But we
should not block waiting for completions. We should handle completions
when they happen. And we should retry submissions until we're done
reading. Retrying if there's no space for the moment.&lt;/p&gt;
&lt;p&gt;Unfortunately the Go library doesn't seem to expose this ring behavior
of io_uring. Or I've missed it.&lt;/p&gt;
&lt;p&gt;But we can do it in Zig. Let's go.&lt;/p&gt;
&lt;h3 id="io_uring,-ring-buffer,-zig"&gt;io_uring, ring buffer, Zig&lt;/h3&gt;&lt;p&gt;We need to change the way we track which offsets we need to submit so
far. We also need to keep the loop going until we are sure we have
&lt;em&gt;written&lt;/em&gt; all data. And we need to stop blocking on the number we
submitted; never blocking at all.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;benchmarkIOUringNEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Allocator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;nEntries&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allocPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;iouring_{}_entries&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;&lt;span class="n"&gt;nEntries&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linux&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IO_Uring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nEntries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deinit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cqes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linux&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;io_uring_cqe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nEntries&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;written&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;written&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;submittedEntries&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubmissionQueueFull&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unreachable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;submittedEntries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;submit_and_wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cqesDone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy_cqes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="n"&gt;cqesDone&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|*&lt;/span&gt;&lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SUCCESS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@intCast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;written&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The code got a bit simpler! Granted, we're omitting error handling.&lt;/p&gt;
&lt;p&gt;Build and run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;zig&lt;span class="w"&gt; &lt;/span&gt;build-exe&lt;span class="w"&gt; &lt;/span&gt;main.zig
$&lt;span class="w"&gt; &lt;/span&gt;./main&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;zig.csv
$&lt;span class="w"&gt; &lt;/span&gt;duckdb&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;select column0 as method, avg(cast(column1 as double)) || 's' avg_time, format_bytes(avg(column2::double)::bigint) || '/s' as avg_throughput from 'zig.csv' group by column0 order by avg(cast(column1 as double)) asc&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;method&lt;/th&gt;
&lt;th&gt;avg_time&lt;/th&gt;
&lt;th&gt;avg_throughput&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;iouring_128_entries&lt;/td&gt;
&lt;td&gt;0.06035423609999999s&lt;/td&gt;
&lt;td&gt;1.7GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iouring_1_entries&lt;/td&gt;
&lt;td&gt;0.0610197624s&lt;/td&gt;
&lt;td&gt;1.7GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;blocking&lt;/td&gt;
&lt;td&gt;0.0671628515s&lt;/td&gt;
&lt;td&gt;1.5GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Not bad!&lt;/p&gt;
&lt;h3 id="crank-it-up"&gt;Crank it up&lt;/h3&gt;&lt;p&gt;We've been inserting 100MiB of data. Let's go up to 1GiB to see how
that affects things. Ideally the more data we write the more we
reflect realistic long-term results.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;main.zig&lt;/code&gt; just change &lt;code&gt;SIZE&lt;/code&gt; to &lt;code&gt;1073741824&lt;/code&gt;. Rebuild and run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;zig&lt;span class="w"&gt; &lt;/span&gt;build-exe&lt;span class="w"&gt; &lt;/span&gt;main.zig
$&lt;span class="w"&gt; &lt;/span&gt;./main&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;zig.csv
$&lt;span class="w"&gt; &lt;/span&gt;duckdb&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;select column0 as method, avg(cast(column1 as double)) || 's' avg_time, format_bytes(avg(column2::double)::bigint) || '/s' as avg_throughput from 'out.csv' group by column0 order by avg(cast(column1 as double)) asc&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;method&lt;/th&gt;
&lt;th&gt;avg_time&lt;/th&gt;
&lt;th&gt;avg_throughput&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;iouring_128_entries&lt;/td&gt;
&lt;td&gt;0.6063814535s&lt;/td&gt;
&lt;td&gt;1.7GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iouring_1_entries&lt;/td&gt;
&lt;td&gt;0.6167537295000001s&lt;/td&gt;
&lt;td&gt;1.7GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;blocking&lt;/td&gt;
&lt;td&gt;0.6831747749s&lt;/td&gt;
&lt;td&gt;1.5GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;No real difference, perfect!&lt;/p&gt;
&lt;p&gt;Let's make one more change though. Let's up the &lt;code&gt;BUFFER_SIZE&lt;/code&gt; from
4KiB to 1MiB.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;zig&lt;span class="w"&gt; &lt;/span&gt;build-exe&lt;span class="w"&gt; &lt;/span&gt;main.zig
$&lt;span class="w"&gt; &lt;/span&gt;./main&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;zig.csv
$&lt;span class="w"&gt; &lt;/span&gt;duckdb&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;select column0 as method, avg(cast(column1 as double)) || 's' avg_time, format_bytes(avg(column2::double)::bigint) || '/s' as avg_throughput from 'out.csv' group by column0 order by avg(cast(column1 as double)) asc&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;method&lt;/th&gt;
&lt;th&gt;avg_time&lt;/th&gt;
&lt;th&gt;avg_throughput&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;iouring_128_entries&lt;/td&gt;
&lt;td&gt;0.2756831357s&lt;/td&gt;
&lt;td&gt;3.8GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iouring_1_entries&lt;/td&gt;
&lt;td&gt;0.27575404880000004s&lt;/td&gt;
&lt;td&gt;3.8GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;blocking&lt;/td&gt;
&lt;td&gt;0.2833337046s&lt;/td&gt;
&lt;td&gt;3.7GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Hey that's an improvement!&lt;/p&gt;
&lt;h3 id="control"&gt;Control&lt;/h3&gt;&lt;p&gt;All these numbers are machine-specific obviously. So what does an
existing tool like
&lt;a href="https://fio.readthedocs.io/en/latest/fio_doc.html"&gt;fio&lt;/a&gt; say?
(Assuming I'm using it correctly. I await your corrections!)&lt;/p&gt;
&lt;p&gt;With a 4KiB buffer size:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;fio&lt;span class="w"&gt; &lt;/span&gt;--name&lt;span class="o"&gt;=&lt;/span&gt;fiotest&lt;span class="w"&gt; &lt;/span&gt;--rw&lt;span class="o"&gt;=&lt;/span&gt;write&lt;span class="w"&gt; &lt;/span&gt;--size&lt;span class="o"&gt;=&lt;/span&gt;1G&lt;span class="w"&gt; &lt;/span&gt;--bs&lt;span class="o"&gt;=&lt;/span&gt;4k&lt;span class="w"&gt; &lt;/span&gt;--group_reporting&lt;span class="w"&gt; &lt;/span&gt;--ioengine&lt;span class="o"&gt;=&lt;/span&gt;sync
fiotest:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;g&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;rw&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;write,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;R&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;4096B-4096B,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;W&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;4096B-4096B,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;T&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;4096B-4096B,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ioengine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sync,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;iodepth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
fio-3.33
Starting&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;process
Jobs:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;f&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
fiotest:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;groupid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;jobs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;err&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2437359&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;Thu&lt;span class="w"&gt; &lt;/span&gt;Oct&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;23&lt;/span&gt;:33:42&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2023&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;write:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;IOPS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;282k,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;BW&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1102MiB/s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;1156MB/s&lt;span class="o"&gt;)(&lt;/span&gt;1024MiB/929msec&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;zone&lt;span class="w"&gt; &lt;/span&gt;resets
&lt;span class="w"&gt;    &lt;/span&gt;clat&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;nsec&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2349&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;54099&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2709&lt;/span&gt;.48,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;stdev&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1325&lt;/span&gt;.83
&lt;span class="w"&gt;     &lt;/span&gt;lat&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;nsec&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2390&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;54139&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2752&lt;/span&gt;.89,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;stdev&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1334&lt;/span&gt;.62
&lt;span class="w"&gt;    &lt;/span&gt;clat&lt;span class="w"&gt; &lt;/span&gt;percentiles&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;nsec&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2416&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2416&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2416&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2448&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2448&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;40&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2448&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2448&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;60&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2480&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;70&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2512&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2544&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;90&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2832&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;95&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3504&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;99&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5792&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;99&lt;/span&gt;.50th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="m"&gt;15296&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;99&lt;/span&gt;.90th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="m"&gt;19584&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;99&lt;/span&gt;.95th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="m"&gt;20096&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;99&lt;/span&gt;.99th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="m"&gt;22656&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;bw&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;KiB/s&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;940856&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;940856&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;per&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;83&lt;/span&gt;.36%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;940856&lt;/span&gt;.00,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;stdev&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.00,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;samples&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;iops&lt;span class="w"&gt;        &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;235214&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;235214&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;235214&lt;/span&gt;.00,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;stdev&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.00,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;samples&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;lat&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;usec&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;4&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;97&lt;/span&gt;.22%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;10&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.03%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;20&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.71%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;50&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.04%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;100&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.01%
&lt;span class="w"&gt;  &lt;/span&gt;cpu&lt;span class="w"&gt;          &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;.35%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;82&lt;/span&gt;.11%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;26&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;majf&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;minf&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;IO&lt;span class="w"&gt; &lt;/span&gt;depths&lt;span class="w"&gt;    &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;4&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;8&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;16&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;32&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%
&lt;span class="w"&gt;     &lt;/span&gt;submit&lt;span class="w"&gt;    &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;4&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;8&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;16&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;32&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nb"&gt;complete&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;4&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;8&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;16&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;32&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%
&lt;span class="w"&gt;     &lt;/span&gt;issued&lt;span class="w"&gt; &lt;/span&gt;rwts:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;total&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,262144,0,0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;short&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,0,0,0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;dropped&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,0,0,0
&lt;span class="w"&gt;     &lt;/span&gt;latency&lt;span class="w"&gt;   &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;window&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;percentile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;.00%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;

Run&lt;span class="w"&gt; &lt;/span&gt;status&lt;span class="w"&gt; &lt;/span&gt;group&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;jobs&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;  &lt;/span&gt;WRITE:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;bw&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1102MiB/s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;1156MB/s&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;1102MiB/s-1102MiB/s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;1156MB/s-1156MB/s&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;io&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1024MiB&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;1074MB&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;929&lt;/span&gt;-929msec
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;1.2GB/s is about in the ballpark of what we got.&lt;/p&gt;
&lt;p&gt;And with a 1MiB buffer size?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;fio&lt;span class="w"&gt; &lt;/span&gt;--name&lt;span class="o"&gt;=&lt;/span&gt;fiotest&lt;span class="w"&gt; &lt;/span&gt;--rw&lt;span class="o"&gt;=&lt;/span&gt;write&lt;span class="w"&gt; &lt;/span&gt;--size&lt;span class="o"&gt;=&lt;/span&gt;1G&lt;span class="w"&gt; &lt;/span&gt;--bs&lt;span class="o"&gt;=&lt;/span&gt;1M&lt;span class="w"&gt; &lt;/span&gt;--group_reporting&lt;span class="w"&gt; &lt;/span&gt;--ioengine&lt;span class="o"&gt;=&lt;/span&gt;sync
fiotest:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;g&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;rw&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;write,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;R&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;1024KiB-1024KiB,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;W&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;1024KiB-1024KiB,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;T&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;1024KiB-1024KiB,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ioengine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sync,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;iodepth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
fio-3.33
Starting&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;process
fiotest:&lt;span class="w"&gt; &lt;/span&gt;Laying&lt;span class="w"&gt; &lt;/span&gt;out&lt;span class="w"&gt; &lt;/span&gt;IO&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;/&lt;span class="w"&gt; &lt;/span&gt;1024MiB&lt;span class="o"&gt;)&lt;/span&gt;

fiotest:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;groupid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;jobs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;err&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2437239&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;Thu&lt;span class="w"&gt; &lt;/span&gt;Oct&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;23&lt;/span&gt;:32:09&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2023&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;write:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;IOPS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3953&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;BW&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3954MiB/s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;4146MB/s&lt;span class="o"&gt;)(&lt;/span&gt;1024MiB/259msec&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;zone&lt;span class="w"&gt; &lt;/span&gt;resets
&lt;span class="w"&gt;    &lt;/span&gt;clat&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;usec&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;221&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1205&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;241&lt;/span&gt;.83,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;stdev&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;43&lt;/span&gt;.93
&lt;span class="w"&gt;     &lt;/span&gt;lat&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;usec&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;228&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1250&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;251&lt;/span&gt;.68,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;stdev&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;45&lt;/span&gt;.80
&lt;span class="w"&gt;    &lt;/span&gt;clat&lt;span class="w"&gt; &lt;/span&gt;percentiles&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;usec&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;225&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;225&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;227&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;227&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;231&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;40&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;233&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;235&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;60&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;239&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;70&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;243&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;249&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;90&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;262&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;95&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;269&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;99&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;302&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;99&lt;/span&gt;.50th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;318&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;99&lt;/span&gt;.90th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1074&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;99&lt;/span&gt;.95th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1205&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;99&lt;/span&gt;.99th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1205&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;lat&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;usec&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;250&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;.96%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;500&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;.85%
&lt;span class="w"&gt;  &lt;/span&gt;lat&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;msec&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.20%
&lt;span class="w"&gt;  &lt;/span&gt;cpu&lt;span class="w"&gt;          &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.26%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;94&lt;/span&gt;.96%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;majf&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;minf&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;IO&lt;span class="w"&gt; &lt;/span&gt;depths&lt;span class="w"&gt;    &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;4&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;8&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;16&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;32&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%
&lt;span class="w"&gt;     &lt;/span&gt;submit&lt;span class="w"&gt;    &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;4&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;8&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;16&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;32&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nb"&gt;complete&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;4&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;8&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;16&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;32&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%
&lt;span class="w"&gt;     &lt;/span&gt;issued&lt;span class="w"&gt; &lt;/span&gt;rwts:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;total&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,1024,0,0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;short&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,0,0,0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;dropped&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,0,0,0
&lt;span class="w"&gt;     &lt;/span&gt;latency&lt;span class="w"&gt;   &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;window&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;percentile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;.00%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;

Run&lt;span class="w"&gt; &lt;/span&gt;status&lt;span class="w"&gt; &lt;/span&gt;group&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;jobs&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;  &lt;/span&gt;WRITE:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;bw&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3954MiB/s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;4146MB/s&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;3954MiB/s-3954MiB/s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;4146MB/s-4146MB/s&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;io&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1024MiB&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;1074MB&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;259&lt;/span&gt;-259msec
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;3.9GB/s is also roughly in the same ballpark we got.&lt;/p&gt;
&lt;p&gt;Our code seems reasonable!&lt;/p&gt;
&lt;h3 id="what's-next?"&gt;What's next?&lt;/h3&gt;&lt;p&gt;None of this is original. &lt;code&gt;fio&lt;/code&gt; is a similar tool, written in C, with
many different IO engines including &lt;code&gt;libaio&lt;/code&gt; and &lt;code&gt;writev&lt;/code&gt; support. And
it has many different IO workloads.&lt;/p&gt;
&lt;p&gt;But it's been enjoyable to learn more about these APIs. How to program
them and how they compare to eachother.&lt;/p&gt;
&lt;p&gt;So next steps could include adding additional IO engines or IO
workloads.&lt;/p&gt;
&lt;p&gt;Also, either I need to understand Iceber's Go library better or its
API needs to be loosened up a little bit so we can get that awesome
ring buffer behavior we could use from Zig.&lt;/p&gt;
&lt;p&gt;Keep an eye out here and on my &lt;a href="https://github.com/eatonphil/io-playground"&gt;io-playground
repo&lt;/a&gt;!&lt;/p&gt;
&lt;h3 id="selected-responses-after-publication"&gt;Selected responses after publication&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;wizeman on lobsters
&lt;a href="https://lobste.rs/s/rimkv3/io_uring_basics_writing_file_disk#c_qvlx5u"&gt;suggests&lt;/a&gt;
measuring at least 30 seconds worth of writing data and
&lt;code&gt;fsync()&lt;/code&gt;-ing if you want to test the entire IO subsystem and not
just hitting the kernel cache.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Digging into io_uring has been on my list for a long time now! This week I finally made made some progress.&lt;br /&gt;&lt;br /&gt;Let's go on a little journey through a few increasingly complex (and useful) implementations of writing a file to disk with io_uring.&lt;a href="https://t.co/gR9K2OQs2R"&gt;https://t.co/gR9K2OQs2R&lt;/a&gt; &lt;a href="https://t.co/TMaC8QYL6k"&gt;pic.twitter.com/TMaC8QYL6k&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1715151609615773965?ref_src=twsrc%5Etfw"&gt;October 19, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Thu, 19 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2023-10-19-write-file-to-disk-with-io_uring.html</guid></item><item><title>Build-it-yourself NAS and why not to use a Raspberry PI for it</title><link>https://jeroenpelgrims.com/diy-nas/</link><description>&lt;h2 id="current-setup"&gt;Current setup&lt;a class="zola-anchor" href="https://jeroenpelgrims.com/diy-nas/#current-setup" title="Click here to get a direct link to this section in your browser's address bar."&gt;§&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Currently my home server setup consists of a &lt;a href="https://en.wikipedia.org/wiki/Lenovo_Yoga_2_Pro"&gt;10 year old laptop&lt;/a&gt;.&lt;br /&gt;
It's a pretty janky setup since the laptop doesn't even have an ethernet port, so it's connected to my network through a USB-to-ethernet cable.
For data storage I'm using a 4TB external harddrive, also connected through a USB cable.&lt;/p&gt;
&lt;p&gt;The 4TB data drive is getting a bit cramped so I'm looking to upgrade both in storage volume as stability.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Photo of my old laptop being a good server" src="https://jeroenpelgrims.com/diy-nas/./home-server.jpg" /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The drive is in the cardboard box below the laptop.
Those network cables at the bottom of the photo? They go to my router which is dangling from them.&lt;br /&gt;
Did I mention my current setup is a bit janky?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="prefab-is-expensive"&gt;Prefab is expensive&lt;a class="zola-anchor" href="https://jeroenpelgrims.com/diy-nas/#prefab-is-expensive" title="Click here to get a direct link to this section in your browser's address bar."&gt;§&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When you go looking for ready-made NAS devices you'll probably bump into either a Synaptic or QNAP device.&lt;br /&gt;
For a 5+bay device you'll quickly end up paying at least &lt;a href="https://tweakers.net/nas/vergelijken/#filter:PY5BC8IwDIX_S84V1uGY9gcMdvDUo3iobdRIt5a0iDj2381APOXly3uPLHBlN4cxFDDn475TWreHiwKaX8jVPlK2GNFXSjOYm4sFFSQOyANhDGAgMz0L_KBNXIW54v8kox_Ft9MKsrujpQ-C6Rq1BT2eSGplKWIbKFZk-WOBtu_1NqftDJ2UTe4tSjctrOv6BQ"&gt;500 EUR&lt;/a&gt; just for the device itself without any drives.&lt;/p&gt;
&lt;p&gt;That's quite a bit of money that could better be used on the drives themselves, or at least to get better hardware for the price.&lt;br /&gt;
So I started looking for possibilities to build a NAS myself.&lt;/p&gt;
&lt;h2 id="raspberry-pi"&gt;Raspberry PI&lt;a class="zola-anchor" href="https://jeroenpelgrims.com/diy-nas/#raspberry-pi" title="Click here to get a direct link to this section in your browser's address bar."&gt;§&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My first thought was why not use a Raspberry PI 4 as the core hardware?&lt;br /&gt;
It has a very low power consumption and the processor &amp;amp; ram are still good enough when comparing with a prefab solution.&lt;/p&gt;
&lt;p&gt;But when actually trying to configure everything I'd need to build a NAS using a PI I came accross quite some downsides.&lt;/p&gt;
&lt;h3 id="no-direct-sata-connection"&gt;No direct SATA connection&lt;a class="zola-anchor" href="https://jeroenpelgrims.com/diy-nas/#no-direct-sata-connection" title="Click here to get a direct link to this section in your browser's address bar."&gt;§&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The drives can only be connected to the PI through the USB ports that are on it.&lt;br /&gt;
I know HATs are a thing, but they're not easily available and often still quite expensive.&lt;/p&gt;
&lt;h3 id="not-enough-high-speed-ports"&gt;Not enough (high speed) ports.&lt;a class="zola-anchor" href="https://jeroenpelgrims.com/diy-nas/#not-enough-high-speed-ports" title="Click here to get a direct link to this section in your browser's address bar."&gt;§&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The PI has 4 USB ports in total, but only 2 of them are USB 3.0 ports.&lt;br /&gt;
USB 2.0's max speed is 480 Mbps (60 MB/s), which is significantly slower than the speed of modern hard drives.&lt;br /&gt;
This means it will be nigh to impossible to connect more than 2 data drives to the PI.&lt;/p&gt;
&lt;h3 id="can-t-check-s-m-a-r-t-data-of-the-drives"&gt;Can't check S.M.A.R.T. data of the drives.&lt;a class="zola-anchor" href="https://jeroenpelgrims.com/diy-nas/#can-t-check-s-m-a-r-t-data-of-the-drives" title="Click here to get a direct link to this section in your browser's address bar."&gt;§&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Hard drives can do a periodic self-check to see if there are any indications of issues.&lt;br /&gt;
This can give you some extra time to find a solution before the drive fails.&lt;br /&gt;
That's a pretty important feature to have in a NAS.&lt;br /&gt;
But it's apparently impossible to use this feature if the drives are &lt;a href="https://superuser.com/questions/154337/is-it-possible-to-check-an-external-usb-drive-health-smart"&gt;connected through USB&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="too-much-power-being-drawn-through-the-pi"&gt;Too much power being drawn through the PI&lt;a class="zola-anchor" href="https://jeroenpelgrims.com/diy-nas/#too-much-power-being-drawn-through-the-pi" title="Click here to get a direct link to this section in your browser's address bar."&gt;§&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We could connect all the drives to the PI using a USB to SATA cable, but that would mean that the PI would need to supply power to all those drives.&lt;br /&gt;
It wouldn't be impossible, but it would make it hard to upgrade to more drives later if we wanted to.&lt;/p&gt;
&lt;h3 id="boot-drive-corruption"&gt;Boot Drive corruption&lt;a class="zola-anchor" href="https://jeroenpelgrims.com/diy-nas/#boot-drive-corruption" title="Click here to get a direct link to this section in your browser's address bar."&gt;§&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Usually the SD card is used as a boot drive for a RPi.&lt;br /&gt;
But SD card corruption in a RPi is a known issue. It can happen by a sudden power loss or even just writing to the card too often.&lt;/p&gt;
&lt;p&gt;A solution for this could be to use an SSD as a boot drive and connect it to the PI using USB.&lt;br /&gt;
But seeing that's again a USB-&amp;gt;SATA protocol conversion and a power draw on the PI that doesn't seem like a super stable solution either.&lt;br /&gt;
And I would like to keep a certain level of stableness for the new NAS.&lt;/p&gt;
&lt;p&gt;Plus I've actually tried getting a PI to boot from USB before and it definitely isn't as easy to set up as many guides lead you to believe.&lt;/p&gt;
&lt;h3 id="conclusion-fickle"&gt;Conclusion: Fickle&lt;a class="zola-anchor" href="https://jeroenpelgrims.com/diy-nas/#conclusion-fickle" title="Click here to get a direct link to this section in your browser's address bar."&gt;§&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;All the downsides above, in addition to the many &lt;em&gt;dongles&lt;/em&gt; make the RPi solution seem quite fickle.&lt;/p&gt;
&lt;h2 id="low-power-pc-configuration"&gt;Low-power PC configuration&lt;a class="zola-anchor" href="https://jeroenpelgrims.com/diy-nas/#low-power-pc-configuration" title="Click here to get a direct link to this section in your browser's address bar."&gt;§&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I started thinking I needed to go back to something that uses some more standard desktop hardware.&lt;br /&gt;
Like a simple motherboard with a modern laptop CPU on it to keep it low power.&lt;/p&gt;
&lt;p&gt;Initially I was looking at Atom CPUs, but eventually I somehow bumped into Intel's new &lt;a href="https://ark.intel.com/content/www/us/en/ark/products/series/231819/intel-processor-n-series.html"&gt;N series&lt;/a&gt; and the &lt;a href="https://ark.intel.com/content/www/us/en/ark/products/231803/intel-processor-n100-6m-cache-up-to-3-40-ghz.html"&gt;N100&lt;/a&gt; in particular.&lt;br /&gt;
It's low-power with a TDP of 6 watts and &lt;a href="https://www.cpubenchmark.net/compare/5157vs2960vs3667vs4472/Intel-N100-vs-Intel-Celeron-J3355-vs-Intel-Celeron-J4125-vs-Intel-Celeron-N5095"&gt;double to quadruple&lt;/a&gt; the processing power of any of the prefab NAS'es (at a lower wattage!).&lt;/p&gt;
&lt;p&gt;I couldn't find a way to buy the cpu by itself, it seems you need to buy it preassembled to a motherboard.&lt;br /&gt;
The one I liked most was the &lt;a href="https://www.asrock.com/mb/Intel/N100DC-ITX/"&gt;AsRock N100DC-ITX&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This board gives me 2 SATA connectors, 1 M.2 connector for the boot drive and a PCIe 3.0 x4 slot.&lt;br /&gt;
All the issues I had with the fickleness of the RPi are solved with this.&lt;br /&gt;
The PCIe slot also allows me to get a SATA controller to increase the total supported (data) drives to 8 if I wanted to.&lt;/p&gt;
&lt;h2 id="component-list"&gt;Component list&lt;a class="zola-anchor" href="https://jeroenpelgrims.com/diy-nas/#component-list" title="Click here to get a direct link to this section in your browser's address bar."&gt;§&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Based on that motherboard I looked at what else I needed for hardware.&lt;br /&gt;
Below you can find an overview of all the elements that went into the NAS:&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Component&lt;/th&gt;&lt;th&gt;Name&lt;/th&gt;&lt;th&gt;Price&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Motherboard+CPU&lt;/td&gt;&lt;td&gt;&lt;a href="https://www.asrock.com/mb/Intel/N100DC-ITX/"&gt;N100DC-ITX&lt;/a&gt;&lt;/td&gt;&lt;td&gt;€ 139.90&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;RAM&lt;/td&gt;&lt;td&gt;&lt;a href="https://www.gskill.com/product/165/185/1567584071/F4-3200C16S-16GIS"&gt;G.Skill Aegis F4-3200C16S-16GIS&lt;/a&gt;&lt;/td&gt;&lt;td&gt;€ 39.90&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;M.2 SSD&lt;/td&gt;&lt;td&gt;&lt;a href="https://www.samsung.com/us/computing/memory-storage/solid-state-drives/980-pcie-3-0-nvme-gaming-ssd-500gb-mz-v8v500b-am/"&gt;Samsung 980 500GB&lt;/a&gt;&lt;/td&gt;&lt;td&gt;€ 42.90&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;HDD 1&lt;/td&gt;&lt;td&gt;&lt;a href="https://storage.toshiba.com/enterprise-hdd/cloud-scale-capacity/mg09-series"&gt;Toshiba MG09 18TB&lt;/a&gt;&lt;/td&gt;&lt;td&gt;€ 277.00&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;HDD 2&lt;/td&gt;&lt;td&gt;&lt;a href="https://www.seagate.com/be/nl/products/enterprise-drives/exos-x/x18/"&gt;Seagate Exos X18 18TB&lt;/a&gt;&lt;/td&gt;&lt;td&gt;€ 292.31&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;19v adapter&lt;/td&gt;&lt;td&gt;Seasonic SSA-0601D 19V - 60W&lt;/td&gt;&lt;td&gt;€ 23.99&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Sata power splitter&lt;/td&gt;&lt;td&gt;&lt;a href="https://akasa.co.uk/search.php?seed=AK-CBPW05-30"&gt;Akasa SATA power splitter 0.3m&lt;/a&gt;&lt;/td&gt;&lt;td&gt;€ 6.66&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Case&lt;/td&gt;&lt;td&gt;&lt;a href="https://www.inter-tech.de/productdetails-156/IT-607_DESKTOP_EN.html"&gt;Inter-Tech IT-607&lt;/a&gt;&lt;/td&gt;&lt;td&gt;€ 42.71&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Sata controller (optional)&lt;/td&gt;&lt;td&gt;&lt;a href="https://www.axagon.eu/en/produkty/pces-sa6"&gt;Axagon PCES-SA6&lt;/a&gt;&lt;/td&gt;&lt;td&gt;€ 67.06&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;Total&lt;/td&gt;&lt;td&gt;€ 932.43&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Compared to a prefab NAS I'm saving about 100-150 EUR, and I get more powerful hardware in return.&lt;/p&gt;
&lt;h3 id="why-2-different-hard-drives"&gt;Why 2 different hard drives?&lt;a class="zola-anchor" href="https://jeroenpelgrims.com/diy-nas/#why-2-different-hard-drives" title="Click here to get a direct link to this section in your browser's address bar."&gt;§&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://community.spiceworks.com/topic/880874-don-t-get-drives-from-the-same-batch"&gt;It is said&lt;/a&gt; that it's not a good idea to buy multiple of the same drives from the same brand at the exact same time. They could be drives that are manufactured in the same batch.&lt;br /&gt;
The production parameters (and defects) that a drive has will also determine how long a drive has before it will eventually fail.&lt;/p&gt;
&lt;p&gt;If drives are from the same batch their production parameters are the same, and thus so is their approximate time left to live. This means that the chance is higher that these drives will fail at times that are close together.&lt;br /&gt;
Which is exactly what you don't want if you need time to copy the data to a replacement drive :)&lt;/p&gt;
&lt;p&gt;Buying drives from a different manufacturer alleviates this problem somewhat.&lt;br /&gt;
Drives with different production parameters will (hopefully) also have different "death times".&lt;/p&gt;
&lt;h3 id="why-no-power-supply-why-the-laptop-adapter"&gt;Why no power supply/why the laptop adapter?&lt;a class="zola-anchor" href="https://jeroenpelgrims.com/diy-nas/#why-no-power-supply-why-the-laptop-adapter" title="Click here to get a direct link to this section in your browser's address bar."&gt;§&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This motherboard has a 19v plug like a laptop. The motherboard then powers the other connexted devires like the harddrives.&lt;br /&gt;
As long as there's only 2 HDDs we don't require a separate power supply like in a desktop.&lt;/p&gt;
&lt;h3 id="the-case"&gt;The case&lt;a class="zola-anchor" href="https://jeroenpelgrims.com/diy-nas/#the-case" title="Click here to get a direct link to this section in your browser's address bar."&gt;§&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I tried to get something that was as compact as possible.&lt;br /&gt;
Seeing I'll only have 2 drives for now this one fits perfectly.&lt;br /&gt;
I am thinking about buying some extra fans &amp;amp; dust covers for the fans though, by default the case
only comes with an 80mm fan on the side.&lt;/p&gt;
&lt;p&gt;I'm fairly happy with the case, the only part which was a bit dissapointing was the incorrect information of the amound of 3,5" slots.
It's supposed to have 2 of those slots, and there's definitely enough place for it in the case, but the screw holes are definitely not matching with how they are on the drives.&lt;br /&gt;
In the end I had to buy a 5,25" to 3,5" slot converter.&lt;/p&gt;
&lt;h2 id="operating-system"&gt;Operating system&lt;a class="zola-anchor" href="https://jeroenpelgrims.com/diy-nas/#operating-system" title="Click here to get a direct link to this section in your browser's address bar."&gt;§&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I don't want to go too deep into this in this post, but you have a few options here.&lt;br /&gt;
The most well known ones are probably:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.openmediavault.org/"&gt;OpenMediaVault&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.truenas.com/"&gt;TrueNAS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://unraid.net/"&gt;Unraid&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I went with OpenMediaVault since it seemed to have all the features I needed.&lt;br /&gt;
I tested it out and it seemed to be quite easy to get started with.&lt;/p&gt;</description><author>Jeroen Pelgrims</author><pubDate>Wed, 18 Oct 2023 23:42:00 GMT</pubDate><guid isPermaLink="true">https://jeroenpelgrims.com/diy-nas/</guid></item><item><title>Review and Analysis: So Good They Can't Ignore You</title><link>https://tylerneylon.com/b/newport_book/newport_book.html</link><description>A thoughtful book summary and review for Cal Newport's So Good They Can't Ignore You. &lt;img src="https://tylerneylon.com/b/newport_book/newport_illustration.png" /&gt;</description><author>tylerneylon.com</author><pubDate>Wed, 18 Oct 2023 21:31:08 GMT</pubDate><guid isPermaLink="true">https://tylerneylon.com/b/newport_book/newport_book.html</guid></item><item><title>Summary: Mindset by Dr. Carol S. Dweck</title><link>https://www.chestergrant.com/summary-mindset-by-dr-carol-s-dweck</link><description>&lt;div class="posthaven-post-body"&gt;&lt;p&gt;&lt;/p&gt;&lt;center&gt;        &lt;div class="posthaven-gallery" id="posthaven_gallery[2062114]"&gt;
                  &lt;p class="posthaven-file posthaven-file-image posthaven-file-state-processed"&gt;
          &lt;img class="posthaven-gallery-image" src="https://phaven-prod.s3.amazonaws.com/files/image_part/asset/3082964/rMXIfH4mDau84KDIuni7L7AVSYA/medium_Cover.png" /&gt;
        &lt;/p&gt;

        &lt;/div&gt;
&lt;/center&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;1. This growth mindset is based on the belief that your basic qualities are things you can cultivate through your efforts, your strategies, and help from others. Although people may differ in every which way—in their initial talents and aptitudes, interests, or temperaments—everyone can change and grow through application and experience.&lt;/p&gt;&lt;p&gt;2. The passion for stretching yourself and sticking to it, even (or especially) when it’s not going well, is the hallmark of the growth mindset. This is the mindset that allows people to thrive during some of the most challenging times in their lives.&lt;/p&gt;&lt;p&gt;3. The fixed mindset makes you concerned with how you’ll be judged; the growth mindset makes you concerned with improving.&lt;/p&gt;&lt;p&gt;&lt;b&gt;4. Not long ago I was interested to read about Marina Semyonova, a great Russian dancer and teacher, who devised a novel way of selecting her students. It was a clever test for mindset. As a former student tells it, “Her students first have to survive a trial period while she watches to see how you react to praise and to correction. Those more responsive to the correction are deemed worthy.”&lt;/b&gt;&lt;/p&gt;&lt;p&gt;5. Is there another way to judge potential? NASA thought so. When they were soliciting applications for astronauts, they rejected people with pure histories of success and instead selected people who had had significant failures and bounced back from them.&lt;/p&gt;&lt;p&gt;6. As a New York Times article points out, failure has been transformed from an action (I failed) to an identity (I am a failure). This is especially true in the fixed mindset.&lt;/p&gt;&lt;p&gt;7. Even in the growth mindset, failure can be a painful experience. But it doesn’t define you. It’s a problem to be faced, dealt with, and learned from.&lt;/p&gt;&lt;p&gt;8. John Wooden, the legendary basketball coach, says you aren’t a failure until you start to blame. What he means is that you can still be in the process of learning from your mistakes until you deny them.&lt;/p&gt;&lt;p&gt;9. People with the fixed mindset tell us, “If you have to work at something, you must not be good at it.” They add, “Things come easily to people who are true geniuses.”&lt;/p&gt;&lt;p&gt;10. It’s also important to realize that even if people have a fixed mindset, they’re not always in that mindset. In fact, in many of our studies, we put people into a growth mindset. We tell them that an ability can be learned and that the task will give them a chance to do that.&lt;/p&gt;&lt;p&gt;11. These experiences make our research participants into growth-minded thinkers, at least for the moment—and they act like growth-minded thinkers, too.&lt;/p&gt;&lt;p&gt;12. Since this was a kind of IQ test, you might say that praising ability lowered the students’ IQs. And that praising their effort raised them.&lt;/p&gt;&lt;p&gt;13. Andrew Carnegie once said, “I wish to have as my epitaph: ‘Here lies a man who was wise enough to bring into his service men who knew more than he.’ ”&lt;/p&gt;&lt;p&gt;14. Jack Welch, Lou Gerstner, and Anne Mulcahy are also fascinating because they transformed their companies. They did this by rooting out the fixed mindset and putting a culture of growth and teamwork in its place.&lt;/p&gt;&lt;p&gt;&lt;b&gt;15. First, it means that our best bet is not simply to hire the most talented managers we can find and turn them loose, but to look for managers who also embody a growth mindset: a zest for teaching and learning, an openness to giving and receiving feedback, and an ability to confront and surmount obstacles.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;16. One problem is that people with the fixed mindset expect everything good to happen automatically.&lt;/p&gt;&lt;p&gt;17. Remember the fixed-mindset idea that if you have ability, you shouldn’t have to work hard? This is the same belief applied to relationships: If you’re compatible, everything should just come naturally.&lt;/p&gt;&lt;p&gt;18. When people with a fixed mindset talk about their conflicts, they assign blame. Sometimes they blame themselves, but often they blame their partner. And they assign blame to a trait—a character flaw.&lt;/p&gt;&lt;p&gt;&lt;b&gt;19. Many educators think that lowering their standards will give students success experiences, boost their self-esteem, and raise their achievement. It comes from the same philosophy as the overpraising of students’ intelligence. Well, it doesn’t work. Lowering standards just leads to poorly educated students who feel entitled to easy work and lavish praise.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;20. He didn’t ask for mistake-free games. He didn’t demand that his players never lose. He asked for full preparation and full effort from them. “Did I win? Did I lose? Those are the wrong questions. The correct question is: Did I make my best effort?” If so, he says, “You may be outscored but you will never lose.”&lt;/b&gt;&lt;/p&gt;&lt;p&gt;21. A growth mindset is about believing people can develop their abilities.&lt;/p&gt;&lt;p&gt;22. These concrete plans—plans you can visualize—about when, where, and how you are going to do something lead to really high levels of follow-through, which, of course, ups the chances of success. So the idea is not only to make a growth-mindset plan, but also to visualize, in a concrete way, how you’re going to carry it out.&lt;/p&gt;&lt;/div&gt;</description><author>Chester Grant</author><pubDate>Wed, 18 Oct 2023 15:55:06 GMT</pubDate><guid isPermaLink="true">https://www.chestergrant.com/summary-mindset-by-dr-carol-s-dweck</guid></item><item><title>Experiments in recreating Rust's try operator in Typescript</title><link>https://blog.martijnarts.com/experiments-in-recreating-rusts-try-operator-in-typescript/</link><description>&lt;p&gt;I love &lt;a href="https://github.com/supermacro/neverthrow"&gt;neverthrow&lt;/a&gt;, &lt;a href="https://gcanti.github.io/fp-ts/modules/Either.ts.html"&gt;io-ts&amp;apos; Either&lt;/a&gt;, or a homemade Result library as much as anyone, but I do miss the &lt;a href="https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator"&gt;&lt;code&gt;?&lt;/code&gt;-operator&lt;/a&gt; in Typescript.&lt;/p&gt;
&lt;p&gt;This is my attempt at recreating it&lt;sup class="footnote-ref"&gt;&lt;a href="#fn1" id="fnref1"&gt;[1]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h1 id="the-basic-idea"&gt;The basic idea&lt;/h1&gt;
&lt;p&gt;There are two core things that Rust&amp;apos;s &lt;code&gt;?&lt;/code&gt;-operator allows us to do: early&lt;/p&gt;</description><author>Martijn Arts</author><pubDate>Wed, 18 Oct 2023 13:17:10 GMT</pubDate><guid isPermaLink="true">https://blog.martijnarts.com/experiments-in-recreating-rusts-try-operator-in-typescript/</guid></item><item><title>crosstable.love</title><link>http://akkartik.name/post/crosstable</link><description>&lt;p&gt;
&lt;a href="https://git.sr.ht/~akkartik/crosstable.love"&gt;crosstable.love&lt;/a&gt; is a
little app I whipped up for tracking standings during the Cricket World Cup,
just to avoid the drudgery of resorting rows as new results come in.

&lt;p&gt;
&lt;a href="https://akkartik.name/images/20231018-crosstable.webm"&gt;
  &lt;video style="width: 80%; margin-left: 10%; border: 2px solid #aaaaff;"&gt;
    &lt;source src="https://akkartik.name/images/20231018-crosstable.webm" /&gt;
  &lt;/video&gt;
&lt;/a&gt;
&lt;div class="btw" style="text-align: right;"&gt;
  video; 20 seconds
&lt;/div&gt;</description><author>Kartik Agaram</author><pubDate>Wed, 18 Oct 2023 10:00:00 GMT</pubDate><guid isPermaLink="true">http://akkartik.name/post/crosstable</guid></item><item><title>Run your own tech blog</title><link>https://zackproser.com/blog/run-your-own-tech-blog</link><description>Control your own destiny, build your personal brand, and master web technologies by running your own tech blog.</description><author>Zachary Proser</author><pubDate>Wed, 18 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/run-your-own-tech-blog</guid></item><item><title>Oracle APEX 23.2 Workflows: A First Look</title><link>https://hartenfeller.dev/blog/apex-23-2-workflows-first-look</link><description>A preview version of Oracle APEX 23.2 just got released on apex.oracle.com. Let's take a look at the new workflow feature.</description><author>Philipp Hartenfeller Blog RSS Feed</author><pubDate>Wed, 18 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://hartenfeller.dev/blog/apex-23-2-workflows-first-look</guid></item><item><title>Building an Advent of Code Solution Showcase</title><link>https://xavd.id/blog/post/building-aoc-showcase/</link><description>Documenting my journey and tech stack selection for building a static site in 2023.&lt;br /&gt;&lt;br /&gt;&lt;a href="https://xavd.id/blog/post/building-aoc-showcase/"&gt;Read the whole thing&lt;/a&gt;.</description><author>The David Brownman Blog</author><pubDate>Wed, 18 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xavd.id/blog/post/building-aoc-showcase/</guid></item><item><title>theft deterrent</title><link>http://blog.onepatchdown.net/society/2023/10/17/theft-deterrent/</link><description>&lt;p&gt;My thought, to help with our problem of theft today (2023) is that Target, et al, copy the Costco model, and require membership for entry into the store.&lt;/p&gt;

&lt;p&gt;Have membership cost $0, but is revocable upon video evidence of stealing shit. Swipe to get into the store, pay when you leave.&lt;/p&gt;</description><author>Blogity blog blog. Journal</author><pubDate>Wed, 18 Oct 2023 00:10:17 GMT</pubDate><guid isPermaLink="true">http://blog.onepatchdown.net/society/2023/10/17/theft-deterrent/</guid></item><item><title>Victory at HackYeah 2023!</title><link>https://kaszkowiak.org/en/blog/hackyeah-2023/</link><description>On 30.09 - 01.10, we took 1st place out of 37 teams in the Health &amp;amp; Well-being track at HackYeah 2023, the largest on-site hackathon in Europe! 🏆</description><author>Blog on Maciej Kaszkowiak</author><pubDate>Tue, 17 Oct 2023 14:30:00 GMT</pubDate><guid isPermaLink="true">https://kaszkowiak.org/en/blog/hackyeah-2023/</guid></item><item><title>Digital Minimalism: Kyocera 902KC</title><link>https://lagomor.ph/2023/10/digital-minimalism-kyocera-902kc/</link><description>&lt;p&gt;In August of this year, I deleted Twitter off of my phone, after what I found to be the &lt;a href="https://www.bbc.com/news/technology-66679922" rel="noopener noreferrer" target="_blank"&gt;final straw&lt;/a&gt; after months of garbage policy changes enforced by the rich egg who now owns the site.&lt;/p&gt;
&lt;p&gt;While I have been using a &lt;a href="https://play.google.com/store/apps/details?id=com.beforesoft.launcher" rel="noopener noreferrer" target="_blank"&gt;minimal smartphone&lt;/a&gt; for years - that is to say, a smartphone with as few apps as I &amp;ldquo;felt possible&amp;rdquo; (more on that later) - what I found after deleting my main digital narcotic was that instead of spending less time on my phone as I expected, I was instead spending just blankly staring at my phone before either reading the news or finding some other way to waste time.&lt;/p&gt;</description><author>Home on Lagomorph</author><pubDate>Tue, 17 Oct 2023 09:00:00 GMT</pubDate><guid isPermaLink="true">https://lagomor.ph/2023/10/digital-minimalism-kyocera-902kc/</guid></item><item><title>Creative Copay Coupons</title><link>https://olshansky.info/posts/creative-copay-coupons/</link><description>The pharmaceutical "Creativity" behind Copay Coupons</description><author>🦉 olshansky 🦁</author><pubDate>Tue, 17 Oct 2023 02:16:36 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/posts/creative-copay-coupons/</guid></item><item><title>Guidelines for addressing requests from colleagues</title><link>https://thomasvilhena.com/2023/10/guideliness-for-requests-from-colleagues</link><description>&lt;p&gt;In the workplace we are constantly receiving requests from colleagues, and the more senior we get, the higher the demand for us. That’s natural and expected as we progress in our carrers and take on leadership roles.&lt;/p&gt;

&lt;p&gt;The way we address these requests has a direct impact on how we are perceived within the company and in the collaborative work environment we want to foster. I see them as opportunities to exercise communication and leadership skills, share knowledge, give feedback and positively impact my team and the business.&lt;/p&gt;

&lt;p&gt;Let’s take a look at how we can approach requests effectively, how seniority plays a role and analyze a few illustrative examples.&lt;/p&gt;

&lt;h1 id="guidelines"&gt;Guidelines&lt;/h1&gt;

&lt;p&gt;In a nutshell, our communication in these situations should be driven by the following guidelines:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Employ empathy and nonviolent communication&lt;/li&gt;
  &lt;li&gt;Promote a cooperative, trusting, and supportive environment&lt;/li&gt;
  &lt;li&gt;Empower colleagues’ &lt;b&gt;autonomy&lt;/b&gt;&lt;/li&gt;
  &lt;li&gt;Advance colleagues’ technical skills&lt;/li&gt;
  &lt;li&gt;Focus on results&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before responding to a request, reflect on whether your communication meets most of the above criteria, and whether it doesn’t directly go against any one of them. If your communication is aligned with this, proceed with confidence.&lt;/p&gt;

&lt;p&gt;Notice I deliberately highlighted ‘autonomy’, since I believe it plays a central role. In this context, it means that we should encourage our colleagues to seek solutions to their requests as autonomously as possible. It’s like the old saying: ‘Give a man a fish, and you feed him for a day; teach a man to fish, and you feed him for a lifetime’ (even though sometimes we’ll have no option other than give the fish - due to short deadlines, lack of experience of the courterpat, etc).&lt;/p&gt;

&lt;h1 id="seniority"&gt;Seniority&lt;/h1&gt;

&lt;p&gt;Before heading to examples, a quick note on how the seniority level from our counterpart affects the way we communicate.&lt;/p&gt;

&lt;p&gt;Junior-level colleagues require more care and attention in communication, as they are in an early stage of professional development and need the consideration and support of more senior colleagues to help them grow. Positive and constructive feedback should be given frequently, and we should push them out of their comfort zone, but in a healthy and considerate way.&lt;/p&gt;

&lt;p&gt;In senior-to-senior relationships I encourage some friction. We need to have the courage to be honest with each other without beating around the bush, and trust in the maturity of our colleagues to receive feedback that is less polished, but constructive and insightful. This can speed things up and foster a more dynamic environment.&lt;/p&gt;

&lt;h1 id="examples"&gt;Examples&lt;/h1&gt;

&lt;p&gt;To illustrate below are examples of typical reponses we often give. The first sentence in red shows an inadequate response according to the guidelines. The following sentence in green shows a more appropriate response. It is important to note that there isn’t only one way to act in each situation, and depending on your background and leadership style, your communication will vary. Additionally, keep in mind that we’re examining scenarios in which junior professionals seek assistance from their senior counterparts.&lt;/p&gt;

&lt;h2&gt;A)&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;div style="color: red;"&gt;“Hi Alice, I don't have time to help you, I'm too busy.”&lt;/div&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a cold response, if you reply like this a lot then you’re not making yourself available to your colleagues, creating the perception that the door to collaboration is closed.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;div style="color: green;"&gt;“Hi Alice, unfortunately I'm very short on time today due to a priority activity I need to finish by the day's end. Is this urgent, or can we come back to this topic tomorrow? You can also check if Bill is available. Please keep me informed either way.”&lt;/div&gt;
&lt;/blockquote&gt;

&lt;p&gt;This response shows adequate empathy, as it not only provides insight into your own situation but also expresses genuine interest in understanding the urgency behind the request. It further demonstrates cooperation by suggesting a schedule for assistance, possibly the next day. It also indicates alternative ways, such as exploring the availability of another colleague, possibly familiar with the subject, who could provide assistance.&lt;/p&gt;

&lt;h2&gt;B)&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;div style="color: red;"&gt;“Bob, you should be able to do this by yourself. Try harder”&lt;/div&gt;
&lt;/blockquote&gt;

&lt;p&gt;This can be characterized as a harsh response, especially for people at a more junior level, as, it potentially brings an unfavorable judgment of the person’s ability, and closes the door to collaboration.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;div style="color: green;"&gt;“Bob, kindly share what you've attempted so far, along with any unsuccessful attempts. This will help me grasp the context and offer more tailored guidance.”&lt;/div&gt;
&lt;/blockquote&gt;

&lt;p&gt;This response aims to comprehend prior efforts through clear and well-crafted communication, naturally fostering the culture of autonomy valued within the company. It conveys the expectation that individuals have made an effort to address the issue independently before seeking assistance.&lt;/p&gt;

&lt;h2&gt;C)&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;div style="color: red;"&gt;“Hi Alice, look, I've explained this to you several times, do some research.”&lt;/div&gt;
&lt;/blockquote&gt;

&lt;p&gt;Again, an example of a harsh response. Even though a subject may have been already discussed in the past, it’s essential to maintain an environment of trust and companionship by employing a more thoughtful communication.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;div style="color: green;"&gt;“Hi Alice, I recall encountering this issue on multiple occasions in the past, and I believe that by revisiting our previous solutions, we can resolve it effectively. Please consider retrieving the approach we took when dealing with a similar situation, such as 'XYZ'. If you have any questions, feel free to reach out. I'm here to assist.”&lt;/div&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here, we assume an honest oversight without passing judgment, recognizing that we can sometimes struggle to recall past situations and apply them to new ones. With time and guidance, we can enhance this skill and progress. This presents a valuable opportunity to support a colleague’s technical development and foster their autonomy.&lt;/p&gt;

&lt;h2&gt;D)&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;div style="color: red;"&gt;“Bob, don't worry, I'll take a look and solve this problem by myself, I think it will be faster. I'll let you know when I'm done.”&lt;/div&gt;
&lt;/blockquote&gt;

&lt;p&gt;At first glance, this response appears cooperative and results-oriented. However, it contradicts the autonomy guideline by depriving the individual who sought help of the opportunity to learn and grow from the challenge. This approach can inadvertently foster dependency, as the person may not acquire the skills to handle similar situations independently in the future.&lt;/p&gt;

&lt;p&gt;Unless there’s a good reason to ‘hijack’ the problem, for instance due to a short deadline, the following approach is recommended:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;div style="color: green;"&gt;“Bob, this is a very interesting problem.  Let's discuss it, and I'll assist you in finding a solution. I anticipate it might arise in future scenarios, making it crucial to solidify this learning.”&lt;/div&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now we see the clear mentoring approach, emphasizing the development of colleagues and a forward-looking perspective.&lt;/p&gt;

&lt;h1 id="closing-thoughts"&gt;Closing thoughts&lt;/h1&gt;

&lt;p&gt;The more senior a team member becomes, the higher the expectation for them to have a positive impact on the team and subsequently in the business, inspiring and pushing their colleagues beyond their comfort zones, and thus earning recognition as a leader among peers.&lt;/p&gt;

&lt;p&gt;It’s far from easy; in fact, it’s quite challenging to assume the responsibilities of mentoring and take a prominent role within the team. Effective time management will be essential to strike a balance between safeguarding our personal time and remaining accessible to the team. This means prioritizing tasks, setting clear boundaries, and ensuring that our schedule allows for both focused work and availability to assist our colleagues. By efficiently managing our time, we can fulfill our leadership roles without becoming overwhelmed or unavailable when needed.&lt;/p&gt;

&lt;p&gt;The leadership challenge, whether in a managerial or technical role, is substantial, but the personal growth it brings is truly worth the effort.&lt;/p&gt;</description><author>thomas vilhena</author><pubDate>Tue, 17 Oct 2023 00:30:00 GMT</pubDate><guid isPermaLink="true">https://thomasvilhena.com/2023/10/guideliness-for-requests-from-colleagues</guid></item><item><title>Suggested readings week 41/2023</title><link>https://www.stefanocosta.org/suggested-readings-week-41-2023/</link><description>&lt;hr /&gt;&lt;h3 id="mapping-any-unknown-industry-in-ten-steps"&gt;&lt;a href="https://steveblank.com/2022/09/20/mapping-the-unknown-the-ten-steps-to-map-any-industry/?ref=stefanocosta.org" rel="nofollow noreferrer noopener"&gt;Mapping Any Unknown Industry in ten steps&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Steve Blank on mapping an industry for building a business plan on top of the map, in a few weeks of investigation (or lunches).&lt;/p&gt;&lt;h3 id="timegpt-1-gpt-for-time-series"&gt;&lt;a href="https://arxiv.org/abs/2310.03589?ref=stefanocosta.org" rel="nofollow noreferrer noopener"&gt;TimeGPT-1: GPT for time series&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;One of the trending topics in generative machine learning research is how to&lt;/p&gt;</description><author>Food for Thought (by Stefano Costa)</author><pubDate>Mon, 16 Oct 2023 17:22:29 GMT</pubDate><guid isPermaLink="true">https://www.stefanocosta.org/suggested-readings-week-41-2023/</guid></item><item><title>Distributed Autonomous Organization</title><link>https://mbutler.org/distributed-autonomous-organization/</link><description>charm.farm is an art collective implemented as a distributed autonomous organization. It was launched on the Ethereum mainnet and structured as a conventional DAO, but its purpose was not primarily financial or governance-driven. The DAO was a scaffold—useful mainly as a coordination and modification layer for a broader experiment. The core question behind charm.farm was [&amp;#8230;]</description><author>mbutler</author><pubDate>Mon, 16 Oct 2023 09:35:15 GMT</pubDate><guid isPermaLink="true">https://mbutler.org/distributed-autonomous-organization/</guid></item><item><title>SSH Hardening with ssh-audit</title><link>https://thoughts.greyh.at/posts/ssh-audit/</link><description>Today I explored ssh-audit, a tool designed to audit SSH configurations. Although it’s an excellent tool, I found the hardening guides somewhat lacking. Hence, I decided to write a detailed walkthrough, ensuring the ssh/sshd configurations are easily readable.</description><author>Terminal Thoughts</author><pubDate>Mon, 16 Oct 2023 07:16:52 GMT</pubDate><guid isPermaLink="true">https://thoughts.greyh.at/posts/ssh-audit/</guid></item><item><title>Slartboz: Bonk and The Beast</title><link>https://synystron.substack.com/p/slartboz-bonk-and-the-beast</link><description>Bonk is not good with gurls.</description><author>Synystron Synlogica</author><pubDate>Mon, 16 Oct 2023 03:16:03 GMT</pubDate><guid isPermaLink="true">https://synystron.substack.com/p/slartboz-bonk-and-the-beast</guid></item><item><title>Everything you've been told about programming is wrong</title><link>https://alesruzicka.net/blog/eng/everyting-is-wrong/</link><description>&lt;p&gt;As someone who has been in software development for almost two decades, I saw massive changes in way software is written.&lt;/p&gt;
&lt;p&gt;I stood before two options. Become a hater and shout at cloud how it&amp;rsquo;s all wrong, or realize, that everything I&amp;rsquo;ve been told about programming is subject to change
and it may be time to revisit my core assumptions about how proper code looks like.&lt;/p&gt;</description><author>English articles on AlesRuzicka.net</author><pubDate>Mon, 16 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://alesruzicka.net/blog/eng/everyting-is-wrong/</guid></item><item><title>Tokenizing Arithmetic expressions - calculator p.1</title><link>https://xnacly.me/posts/2023/calculator-lexer/</link><description>In depth guide on tokenizing input + interpreter setup</description><author>xnacly - blog</author><pubDate>Mon, 16 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xnacly.me/posts/2023/calculator-lexer/</guid></item><item><title>Editor Fluency</title><link>https://backendhance.com/blog/2023/editor-fluency/</link><description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;p&gt;Heute habe ich auf der KKON den Vortrag &amp;ldquo;Pragmatic Programming mit Kotlin&amp;rdquo; gehalten.&lt;/p&gt;
&lt;p&gt;Das war wieder ein großer Spaß. Und weil am Freitag noch die &lt;a href="https://www.linkedin.com/posts/marcus-held_marcus-held-rabbitmq-summit-activity-7072497264073535488-0Hjr" rel="noopener noreferrer" target="_blank"&gt;RabbitMQ Summit&lt;i class="bx bx-link-external"&gt;&lt;/i&gt;&lt;/a&gt;
und zwei Wochen danach die &lt;a href="https://www.linkedin.com/posts/marcus-held_%3F%3F%3F%3F-%3F%3F-%3F%3F%3F%3F%3F%3F%3F%3F%3F%3F-%3F%3F%3F-activity-7107233883590377472-TTmp" rel="noopener noreferrer" target="_blank"&gt;W-JAX&lt;i class="bx bx-link-external"&gt;&lt;/i&gt;&lt;/a&gt;
ansteht, hatte ich wirklich keine Lust weitere Slides anzulegen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Also live coding.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Damit bin ich bestimmt schneller in der Vorbereitung (tm).&lt;/p&gt;
&lt;p&gt;Aber abgesehen von meiner Faulheit, gab es noch einen anderen Grund warum ich das gemacht habe.&lt;/p&gt;</description><author>Backendhance</author><pubDate>Mon, 16 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/editor-fluency/</guid></item><item><title>Estimates are about time, so let's cut to the chase</title><link>https://ntietz.com/blog/estimate-in-time/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;As software engineers, we routinely estimate our work.
Our most common brush with estimates is when we estimate individual tasks within a sprint.
Usually, we do that with abstract points, and that's the wrong way about it.
We should be cutting to the chase and estimating directly in units of time.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: Although this post reads as a strong opinion ("x is wrong, do y"), the subject is much more nuanced than that.&lt;/em&gt;
We've used points on most teams I've been on, and it's fine!
I just think we can all do better, maybe!&lt;/p&gt;
&lt;h1 id="why-estimate-at-all"&gt;Why estimate at all?&lt;/h1&gt;
&lt;p&gt;When you get an estimate from an electrician&lt;sup class="footnote-reference"&gt;
  &lt;a href="https://ntietz.com/blog/estimate-in-time/#fn-1" id="fn-ref-1"&gt;1&lt;/a&gt;
&lt;/sup&gt;
, you would be frustrated to get back a number of points.
You typically want to know two things: how long will it take, and how much will it cost?
These two are related but distinct: if it takes a week to replace your panel, that's too long to be without power.
And if it costs $20,000 to change an outlet, that's too high and you'll look elsewhere.
If they give the estimate in points, that may be meaningful to them, but not to you.&lt;/p&gt;
&lt;p&gt;This is true for software engineering, as well.
When we look at large pieces of a product roadmap, we typically need a ballpark understanding of the time and cost.
That lets us prioritize and decide if a feature is worth developing or not.
Despite what we like to tell ourselves, "it'll be done when it's done" isn't a reasonable answer.&lt;/p&gt;
&lt;p&gt;But estimates are useful even just for themselves.
As I've &lt;a href="https://ntietz.com/blog/even-bad-estimates-valuable/"&gt;written before&lt;/a&gt;, estimates are useful even just for the exercise of estimating.
You cannot estimate a task you don't understand well, so if you try to give a good estimate it will encourage you to think deeply about and explore the task at hand.
This leads to better software development, since you come out of planning with a more thorough understanding of what you'll build.&lt;/p&gt;
&lt;p&gt;You see this in other fields, too.
When a general contractor gives you an estimate for your house addition, an oracle that gives the cost and timeline would not be sufficient.
No, she needs the information from doing the estimate, which informs scheduling staff, when materials must be obtained, where the problems in the project will be—and if there are any major headaches waiting.&lt;/p&gt;
&lt;p&gt;So those are generally why we do estimates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;to understand the timeline and cost&lt;/li&gt;
&lt;li&gt;to benefit from the process of estimation&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="points-are-a-proxy-for-time"&gt;Points are a proxy for time&lt;/h1&gt;
&lt;p&gt;On most teams I've been on, people estimate using story points.
The premise is that you can give an abstract number of points to each task, following some sequence.
Some tasks will be 1 point, others 2 or 3 or 5, or even 13 or (shudder) 21.&lt;/p&gt;
&lt;p&gt;Since these points are just abstract units, we have to wait to get some data.
After a few sprints, you'll see how many points the team completes each sprint on average.
Then you can use that to plan: if you can complete 50 points in a sprint, only pull that many points into the sprint, and we'll probably get it done.&lt;/p&gt;
&lt;p&gt;But it's a proxy for time.
When you're estimating, you've got two choices: think about the complexity of the task or think about the time of the task.
But even the complexity comes down to &lt;em&gt;time&lt;/em&gt; because it's premised on the idea that a more complicated task will take longer, so we'll put fewer in the sprint.
So we're thinking about time when estimating the number of points.
Will this task take about as long as another 3 point task, or as another 5 point ticket?
Which is it more similar to?&lt;/p&gt;
&lt;p&gt;And even our data aggregation is a proxy for time.
We estimate the number of points per period, and the number of points for a task, so that we can compute... tasks per period or time per task.
It's a relatively straightforward calculation but it's still a calculation we have to do, and we do it in the backs of our heads.&lt;/p&gt;
&lt;h1 id="cut-to-the-chase-with-time"&gt;Cut to the chase with time&lt;/h1&gt;
&lt;p&gt;It's much clearer and easier to handle when we just go straight to time.
Let's look at a few scenarios and how we have to handle them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;There's a holiday or PTO during this sprint.&lt;/strong&gt;
If you know how much time is lost, then by estimating in units of time rather than points, you can just... adjust it.
In contrast, if you're using points you have to figure out how many points the Pi Day holiday accounts for, or how many points Sam's PTO will cost us.
Some engineers' PTO will reduce your sprint point balance by more than others.
If you estimated in time, you just... don't include those tasks.
Note: this &lt;em&gt;does assume&lt;/em&gt; that you estimate tasks relative to the assignee; that is perhaps equally contentious...&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Meetings, breaks, and email are not accounted for in time estimates.&lt;/strong&gt;
They're really not accounted for in either points or in time.
But with time, you have a built-in extra metric that you get out: you can see hours of "real" work per week vs. overhead.
This is probably a scary number, and one that folks outside of engineering may be surprised by ("aren't you paid to write code??").
Getting it is a feature, though, because it lets you easily ask the question of if the overhead is worth it and appropriate.
Teams often get meeting creep, so this can be a nice check.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Someone joins (or leaves) the team.&lt;/strong&gt;
With points, you have to kind of fudge the sprint points to account for a new team member.
Does this person add 10 points, or 20?
You have to wait a few sprints to establish a new baseline.
With time, you just have to estimate tasks assigned to them, and any overrun will be just in their tasks, and then you can work with them to adjust their estimates.
And that's easy, because you don't have to explain what a 3 point task represents.
Everyone already knows how long an hour is.&lt;/p&gt;
&lt;p&gt;To my eye, everything about estimates is easier when you use units of time directly.
You're not using a proxy measure of time, you're just using the time itself.&lt;/p&gt;
&lt;h1 id="when-it-all-goes-wrong-beware-the-traps"&gt;When it all goes wrong: beware the traps&lt;/h1&gt;
&lt;p&gt;It's not all sunshine 🌞 and rainbows 🌈.
There are definite traps with estimating in units of time, and these are part of why people avoid it.
I think the trade-off is still in time's favor, but they're important to keep in mind.
Your situation or judgment may differ from mine.&lt;/p&gt;
&lt;p&gt;Sometimes, people outside of the team will abuse time estimates.
They might promise a feature to a customer because they saw its completion would take X hours only.
This is a huge problem and red flag.
If this is happening, you &lt;em&gt;may&lt;/em&gt; mitigate it by hiding your estimates or estimating in cryptic units instead.
I would suggest that if this happens, though, things are rotten to the core and there's a bigger problem in the org.&lt;/p&gt;
&lt;p&gt;Another problem with time estimates is that it &lt;em&gt;does&lt;/em&gt; make overhead visible.
It makes it harder to hide certain things, like professional development, when hours all become accounted for.
As engineers, we should have time to do our professional development at work, but many employers resist this.
If your organization would use time estimates to hold you to cranking out code for 30+ hours a week: first, change to story points; and second, &lt;em&gt;run&lt;/em&gt;.
When you can, get out of that sort of environment.
I promise you, there are employers who will not only allow but will encourage your professional development.&lt;/p&gt;
&lt;p&gt;And with estimating time, since you have to estimate &lt;em&gt;relative to the assignee&lt;/em&gt;, this really falls down if you want to shuffle tasks around.
Maybe Nicole can complete it in 2 hours but John would need 8 hours for it.
With abstract points, this kind of comes out in the wash eventually, but with time it's &lt;em&gt;glaringly&lt;/em&gt; obvious that something changed.
I think this is a mixed blessing, but if you're on a team that likes to shuffle tasks, or estimate &lt;em&gt;without&lt;/em&gt; the assignee it's simply unworkable.
So if you are supremely flexible with task assignees, then time simply won't work.&lt;/p&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 16 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/estimate-in-time/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Flowbased: update 2 about the the Nordic Flowbased parallel run results</title><link>https://boerman.dev/posts/flowbased/nordic_flowbased3/</link><description>&lt;p&gt;Recently, it &lt;a href="https://nordic-rcc.net/go-live-of-nordic-flow-based-capacity-calculation-delayed/"&gt;was announced&lt;/a&gt; that the Nordic Flowbased go-live was delayed due to IT issues. While this is obviously a pity to hear, other positive things have been announced.&lt;br /&gt;
The evaluation report from NRAs &lt;a href="https://nordic-rcc.net/wp-content/uploads/2023/06/Parallel-run-report_final_public.pdf"&gt;was published&lt;/a&gt;. It was evaluated and &lt;a href="https://www.nordicenergyregulators.org/2023/07/nra-communication-regarding-the-tsos-june-2023-epr-report/"&gt;found sufficient by the NRAs&lt;/a&gt;, although with some extra remarks.&lt;br /&gt;
A very interesting summary containing issues found and lessons learned, &lt;a href="https://nordic-rcc.net/wp-content/uploads/2023/06/Operational-learning-points.pdf"&gt;was published&lt;/a&gt; alongside the report. In there a point-by-point analysis is done for specific issues that were encountered during the parallel run. A very interesting read for experts!&lt;/p&gt;</description><author>Blog Frank Boerman</author><pubDate>Mon, 16 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://boerman.dev/posts/flowbased/nordic_flowbased3/</guid></item><item><title>Two Years with Legacy Code</title><link>https://bastibe.de/2023-10-15-one-year-with-legacy-code.html</link><description>&lt;p&gt;From January 2021 to the beginning of 2023, I worked on a legacy code base at Fraunhofer IDMT in Oldenburg. My task was the maintenance and development of a DNN-based speech recognition engine that had become terra incognita when its original developer had left the company a year before I started. The code had all the hallmarks of severe technical debt, with layers of half-used abstractions, many unused branches of unknown utility, and the handwriting of several concurrent programmers at odds with each other.&lt;/p&gt;
&lt;p&gt;The code had evidently been written in a mad dash to bring the product to market. And not to discredit its developers, had been in production for several years, with a core of robust algorithms surrounded by helper scripts that had allowed the company to build upon, even after the original developers had left.&lt;/p&gt;
&lt;p&gt;It was my job to clean it up. Having spent six years on my PhD recently, I welcomed the calmer waters of 'just' programming for a bit. This blog post is a summary of the sorts of challenges I faced during this time, and what kinds of techniques helped me overcome them.&lt;/p&gt;
&lt;h2&gt;The lay of the land&lt;/h2&gt;
&lt;p&gt;I approached the task from the outside, sorting through the build scripts first. Evidently, at least three authors were involved: One old-school Unix geek that wrote an outdated dialect of CMake, one high-level Python scripter, and one shell scripter that deeply believed in abstraction-by-information-hiding. The result of this was... interesting.&lt;/p&gt;
&lt;p&gt;For a good few weeks I &amp;quot;disassembled&amp;quot; these scripts by tracing their execution manually through their many layers, and writing down the necessary steps that were actually executed. My favorite piece of code was a Makefile that called a shell script that ran a Python program, which instantiated a few classes and data structures, which ultimately executed &amp;quot;configure; make; make install&amp;quot; on another underying Makefile. I derived great satisfaction from cutting out all of these middle-men, and consolidating several directories of scripts into a single Makefile.&lt;/p&gt;
&lt;p&gt;Similar simplifactions were implemented at the same time across several code bases by my colleagues. In due time, this concerted effort enabled us to implement continuous integration, automated benchmarking, and automated builds, but more on that later.&lt;/p&gt;
&lt;h2&gt;Data refactoring&lt;/h2&gt;
&lt;p&gt;The speech recognition software implemented a sort of interpreter for the DNN layers, originally encoded as a custom binary blob. Apparently, a custom binary approach had been taken to avoid dependencies on external parsing libraries. Yet the data had become so convoluted that both its compilation and its parsing were now considered unchangeable black boxes that impeded further development.&lt;/p&gt;
&lt;p&gt;Again, I traced through the execution of the compiling code, noted down the pieces of data it recorded, and rewrote the compiler to produce a MsgPack file. On the parsing side, I wrote a custom MsgPack parser in C. Looking back, every job I've had involved writing at least a couple of data dumpers/parsers, yet many developers seem intimidated by such tasks. But why write such a thing yourself instead of using an off-the-shelf solution? In an unrelated code review later in the year one colleague used the cJSON library for parsing JSON; in the event, cJSON was several magnitudes bigger and more complex than the code base it was serving, which is clearly absurd. Our job as developers is to manage complexity, including that of our dependencies. In cases such as these, I often find a simple, fit-for-purpose solution preferable to more generalized external libraries.&lt;/p&gt;
&lt;p&gt;A part of the DNN data came from the output of a training program. This output however was eternally unstable, often breaking unpredictably between version, and requiring complex workarounds to accommodate different versions of the program. The previous solution to this was a deeply nested decision tree for the various permutations the data could take. I simplified this code tremendously by calling directly into the other program's libraries, instead of trying to make sense of its output. This is another technique I had to rely on several times, hooking into C/C++ libraries from various Python scripts to bridge between data in a polyglot environment.&lt;/p&gt;
&lt;p&gt;Doing these deep dives into data structures often revealed unintended entanglements. In order to assemble one data structure, you had to grab pieces of multiple different source data. Interestingly, once data structures were cleaned up to no longer have such entanglements, algorithms seemed to fall into place effortlessly. However, this was not a one-step process, but instead an ongoing struggle to keep data structures minimal and orthogonal. While algorithms and functions often feel easier to refactor than data structures, I have learned from this that it is often the changes to data structures that have the greatest effect, and should therefore receive the greatest scrutiny.&lt;/p&gt;
&lt;h2&gt;Code refactoring&lt;/h2&gt;
&lt;p&gt;My predecessor had left me a few screen casts by way of documentation. While the core program was reasonably well-structured, it was embedded in an architectural curiosity that told the tale of a frustrated high-level programmer forced to do low-level gruntwork. There were poor-man's-classes implemented as C structs with function pointers, there were do-while-with-goto-loops for exception handling, there were sort-of-dynamically-typed data containers, accompanied by angry comments decrying the stupidity of C.&lt;/p&gt;
&lt;p&gt;Now I like my high-level-programming as much as the next guy, but forcing C to be something it isn't, is not my idea of fun. So over a few months I slowly removed most of these abstractions. Somewhat to my surprise, most of them turned out pure overhead that could simply be removed. Where a replacement was needed, I reverted to native C constructs. Tagged unions instead of casting, variable-length-arrays instead of dynamic arrays. Treating structs as values instead of references. This, alone, reduced the entire code base by a good 10%. The harder part was sorting out the jumble of headers and dependencies that had evidentally built up over time. Together with the removal of dead code paths, the overall code base shrank by almost half. There are few things more satisfying than excising and deleting unnecessary code.&lt;/p&gt;
&lt;p&gt;I stumbled upon one particularly interesting problem when trying to integrate another code base into ours. Within our own software, build times were small enough to make logging and printf-debugging easier than an interactive debugger such as GDB. The other code base however was too complex to recompile on a whim, and a different solution had to be found. Now I am a weird person who likes to touch the raw command line instead of an IDE. And in this case this turned out to be a huge blessing, as I found that GDB can not only be used interactively, but can also be scripted! So instead of putting logging into the other library, I wrote GDB scripts that augmented break points with a little &lt;code&gt;call printf(...)&lt;/code&gt; or &lt;code&gt;print/d X&lt;/code&gt;. These could get suprisingly complicated, where one breakpoint might enable or disable other breakpoints conditionally, and break point conditions could call functions on their own. It took some learning, but these debugging scripts were incredibly powerful, and a technique I will definitely refer to in the future.&lt;/p&gt;
&lt;p&gt;When adding new features to the software, I often found it impossible to work the required data flow into the existing program code without snowballing complexity. I usually took these situations as code smells that called for a refactoring. Invariably, each cleaning up of program flow or data structures inched the program closer and closer to allow my feature addition. After a while, this became an established modus operandi: independently clean the code until feature additions become easy and obvious, then do the obvious thing. Thus every task I finished also left the surrounding code in a better state. In the end, about 80% of the code base had gotten this treatment, and I strongly believe that this has left the project in a much better state than it was before. To say nothing of the added documentation and tests, of course.&lt;/p&gt;
&lt;h2&gt;More velocity makes bigger craters&lt;/h2&gt;
&lt;p&gt;As I slowly shifted from cleanup work to new features, change management became a pressing issue. New features had to be evaluated, existing features had to be tested, and changes had to be documented and downstreamed. Fascinatingly, the continuous integration and evaluation tools we built for this purpose, soon unearthed a number of hidden problems in other parts of the product that we had not been aware of (including that the main task I had been hired to do was less worthwhile than thaught, LOL). That taught us all a valuable lesson about testing, and proving our assertions. That said, I never found bottom-level unit tests all that useful for our purposes; the truly useful tests invariably were higher-level integration tests.&lt;/p&gt;
&lt;p&gt;Eventually, my feature additions led to downstream changes by several other developers. While I took great care to present a stable API, and documenting all changes and behavior appropriately, at the end of the day my changes still amounted to a sizeable chunk of work for others. This was a particularly stark contrast to the previous years of perfect stagnation while nobody had maintained the library. My main objective at this point was to avoid the mess I had started out with, where changes had evidentally piled on changes until the whole lot had become unmaintainable.&lt;/p&gt;
&lt;p&gt;Thus a balance had to be struck between moving fast (and breaking things), and projecting stability and dependability. One crucial tool for this job turned out to be code reviews. By involving team members directly with the code in question, they could be made more aware of its constraints and edge cases. It took a few months to truly establish the practice, but by the end of a year everyone had clearly found great value in code reviews as a tool for communication.&lt;/p&gt;
&lt;h2&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;There is a lot more to be said about my time at Fraunhofer. The deep dive into the world of DNN engines was truly fascinating, as were the varied challenges of implementing these things on diverse platforms such as high-performance CPU servers, Laptops, Raspberry Pis, and embedded DSPs. I learned to value automation of developer tasks, and of interface stability and documentation for developer productivity.&lt;/p&gt;
&lt;p&gt;But most of all, I learned to appreciate legacy code. It would have been easy to call it a &amp;quot;mess&amp;quot;, and advocate to rewrite it from scratch. But I found it much more interesting to try to understand the code's heritage, and tease out the algorithmic core from the abstractions and architectural supports. There were many gems to be found this way, and a lot to be learned from the programmers before you. I often felt a strange connection to my predecessor, as if we were talking to each other through this code base. And no doubt my successor feels the same way about my code now.&lt;/p&gt;</description><author>bastibe.de</author><pubDate>Sun, 15 Oct 2023 18:01:00 GMT</pubDate><guid isPermaLink="true">https://bastibe.de/2023-10-15-one-year-with-legacy-code.html</guid></item><item><title>The Freedom to Choose</title><link>https://marctreagan.medium.com/the-freedom-to-choose-40edcdefe3fe?source=rss-39742e0e4fea------2</link><description>&lt;p&gt;Well it had happened again. The little blue cross had appeared. This cross signaled not salvation but possible damnation. The potential to lose control of one’s destiny.&lt;/p&gt;&lt;p&gt;The woman Jeff has been seeing is pregnant. Amy texted a picture of a positive pregnancy test with no context. None was needed. He stared at the picture, contemplating his fate. He barely knew this girl. Well not quite, they had been seeing each other casually for a few months now.&lt;/p&gt;&lt;p&gt;They had been careful by some people’s definition. Condoms the first few times but then simply relying on birth control and coitus interruptus. Truth be told he liked her a lot. But even if she had been the most perfect woman on earth the decision was already made in his mind. They simply were not at a place to have a child even if they had potential as a couple.&lt;/p&gt;&lt;p&gt;He considered the possible outcomes and how to prepare for them. She didn’t seem like the type of girl to bear the child of a man she hardly knew. She was beautiful, brilliant, and accomplished. She had a masters degree in bioengineering and worked at a large pharma company.&lt;/p&gt;&lt;p&gt;They had briefly discussed their religious and political views. But when beliefs collide with reality you never know what might happen. He knew he was legally powerless when it came to ending a pregnancy. But he had other means at his disposal.&lt;/p&gt;&lt;p&gt;Jeff had a hard rule against discussing serious things over text. There were too many opportunities for misunderstanding. He replied simply: “I’m coming over.”&lt;/p&gt;&lt;p&gt;Jeff knocks on Amy’s door.&lt;/p&gt;&lt;p&gt;Amy: “Hey”&lt;/p&gt;&lt;p&gt;Jeff: “Hey, how are you feeling?”&lt;/p&gt;&lt;p&gt;Amy: “I don’t know how I’m feeling. I’m calmer than I would have thought. I mean I know what I have to do. It’s just the doing it that may be difficult”&lt;/p&gt;&lt;p&gt;Jeff: “What do you mean exactly?”&lt;/p&gt;&lt;p&gt;Amy: “Well I’m in no place to raise a child and while I think I like you I really don’t know you.”&lt;/p&gt;&lt;p&gt;The relief washed over Jeff. There would be no need to argue, beg, or manipulate. He wasn’t going to have to threaten to change his identity or to leave the country. Not that he truly would. He would do what was right no matter the final decision.&lt;/p&gt;&lt;p&gt;Jeff: “So you just think you like me huh?”&lt;/p&gt;&lt;p&gt;They smile at each other.&lt;/p&gt;&lt;p&gt;Jeff: “You’re right, we are in no place to take this on right now. I’ll help in any way I can”&lt;/p&gt;&lt;p&gt;Amy: “I’m devastated about this but I’m also sad that this means our time is over”&lt;/p&gt;&lt;p&gt;Jeff: “You don’t know that. I think I like you too. Neither of us know the future. Let’s take care of this and take it from there. It’s all we can do right now.”&lt;/p&gt;&lt;p&gt;Amy: “Always the practical one.”&lt;/p&gt;&lt;p&gt;Jeff had knocked up a girl once before, 10 years ago when he was still in school. A college where the football scores are more important than graduation rates. This was before Roe v. Wade was overturned so you still had legal access to abortion in every state but with quite a few hellish hoops to jump through in some of them.&lt;/p&gt;&lt;p&gt;You had to listen to a morally conflicted doctor recite a script she disagreed with on why the dangers of abortion were far greater than those of pregnancy. The doctor was also legally required to perform an ultrasound and make you listen to the heartbeat. Then you had to suppress your guilt for a minimum of 48 hours while you waited for the next appointment to get either the pills or the procedure. All the result of legislation enacted by those who wanted to do everything in their power to get you to change your mind.&lt;/p&gt;&lt;p&gt;Before any of this even occurred, you potentially had to drive hundreds of miles to one of just two clinics in the state legally permitted to perform abortions. Then you had to wade through a crowd of protestors calling you a whore and waving signs saying “Abortion is Murder.” But at least there was some armed security to ensure things didn’t get too out of hand.&lt;/p&gt;&lt;p&gt;These memories made him recoil. But he knew what must be done.&lt;/p&gt;&lt;p&gt;The world had changed in the intervening decade. Roe v. Wade was overturned by a conservative supreme court and immediately 24 states enacted abortion bans. 16 of them, mostly in the southeast, have no exemptions for rape or incest. The south once again lives up to its reputation of “keeping it in the family.”&lt;/p&gt;&lt;p&gt;Amy: “I’m so nervous with Roe v. Wade being overturned last year”&lt;/p&gt;&lt;p&gt;Jeff: “Don’t worry. We’re lucky enough we can afford to go out of state. Try to think of it as a vacation”.&lt;/p&gt;&lt;p&gt;Amy smiles gloomily.&lt;/p&gt;&lt;p&gt;Being in the deep south they were surrounded by a double ring of states which had banned abortion. An impressive defensive fortification assembled to protect what conservatives believed were otherwise defenseless lives that began at conception. But Jeff and Amy had access to airpower and the enemy had no anti-air defenses. They proceeded to do some research.&lt;/p&gt;&lt;p&gt;Jeff: “I like how all the articles are titled ‘The Best States for Women’s Reproductive Healthcare.’”&lt;/p&gt;&lt;p&gt;Amy: “Well they can’t exactly title them ‘The best states to get a quick abortion, with no questions asked.’”&lt;/p&gt;&lt;p&gt;Jeff: “California, New York, and Maine seem like the best options.”&lt;/p&gt;&lt;p&gt;Amy: “I really don’t want this to be my first thought when visiting California or New York, also I’ve never been to Maine.”&lt;/p&gt;&lt;p&gt;Jeff trying to sound cheerful: “Maine is beautiful this time of year.”&lt;/p&gt;&lt;p&gt;Jeff still felt a lot of guilt about what happened 10 years ago. He was not emotionally supportive of his girlfriend at the time. He paid for the procedure but was distant and uncaring. He didn’t want to make the same mistakes. He offered to go with Amy and she gladly accepted.&lt;/p&gt;&lt;p&gt;They booked flights, a 2-night stay at a bed and breakfast, and an appointment at a clinic in Augusta. There were many spots available and the cost was transparently shown on the website: $500. Jeff recalled the $400 price tag from 10 years before. At least abortion hadn’t kept up with inflation.&lt;/p&gt;&lt;p&gt;Jeff: “So get this, the CDC says there were 620,000 US abortions in 2019. 24 states have banned abortion so we can roughly assume that 310k women now need to fly to other states for abortions. If you assume an average ticket price of $500 that’s a $310 million increase in annual revenue for the airlines. I wonder if Delta’s lobbyists were pushing for this.”&lt;/p&gt;&lt;p&gt;Amy: “Stop being such a conspiracy theorist, I just want to get this over with.”&lt;/p&gt;&lt;p&gt;Having white collar corporate jobs they were both able to take off work at short notice. Amy packed her bags and Jeff went home to do the same. They shared an awkward goodbye.&lt;/p&gt;&lt;p&gt;Jeff picked up Amy the next morning and they headed to the airport. As the plane took off they felt a bit like refugees fleeing a tyrannical theocracy. But going through airport security isn’t quite the same as crossing the DMZ or Checkpoint Charlie under a hail of bullets.&lt;/p&gt;&lt;p&gt;They have a safe flight, pick up their rental car, and arrive at their bed and breakfast. The hosts are the stereotypically kind elderly couple who often run these establishments. Doing their best to meet all of your needs while trying not to be too overbearing about it.&lt;/p&gt;&lt;p&gt;They exchange pleasantries and inform their hosts that they are here for a much-needed break from their stressful corporate jobs. They explore the town for a bit and go on a short hike before coming back and calling it an early night. The appointment is early the next morning.&lt;/p&gt;&lt;p&gt;Jeff had told Amy he had gone through this experience before but spared her the details as he didn’t want to upset her more than she already was. When they arrived the next morning, there were no protestors out front which was a welcome sign.&lt;/p&gt;&lt;p&gt;After checking in they wait maybe seven minutes to be called back. The nurse asks some basic medical questions and then tells them to wait for the doctor. This begins the awkward interval when you are alone in the examination room with exactly no idea how long you are going to be waiting. Thankfully they hear a respectful double knock a mere 30 seconds later.&lt;/p&gt;&lt;p&gt;The doctor asks Amy if she wants Jeff to be here while casting a slightly contemptuous look his way. Amy gives an emphatic yes and the doctor changes her demeanor towards him. The doctor explains the process and recommends the medication as Amy is maybe 6 weeks along. Amy takes the first two pills while still in the clinic and the doctor provides the second round to be taken 24 hours later. She warns of cramping and bleeding and recommends taking her preferred OTC pain reliever. The doctor also states that the official recommendation is no sexual activity for two weeks while giving a stern look towards Jeff. He tries to look as innocent as possible. The whole process takes less than an hour. Since it’s still very early they go grab breakfast.&lt;/p&gt;&lt;p&gt;Jeff: “I have to say that was the polar opposite of my previous experience, no protestors, no forced ultrasound, no second appointment. Maybe I should move here”&lt;/p&gt;&lt;p&gt;Amy: “Wow, I didn’t know people had to go through that, seems inhuman”&lt;/p&gt;&lt;p&gt;Jeff: “I guess it’s just the result of people just doing what they think is right. These pills could kick in at any time, do you want to go back to the bed and breakfast?”&lt;/p&gt;&lt;p&gt;Amy: “Let’s do something, I need a distraction right now”&lt;/p&gt;&lt;p&gt;They go to a couple of historic sites and a museum followed up by lobster rolls for lunch.&lt;/p&gt;&lt;p&gt;Amy begins to feel the pills starting to work. They wrap up their tour of Augusta and head back to the bed and breakfast for the worst of it. Jeff brings her water, heating pads, and her favorite chocolate ice cream but otherwise sits around feeling useless.&lt;/p&gt;&lt;p&gt;Amy: “You know I could get used to you waiting on me like this” she says trying to fake a smile. He admires her casualness but knows her suffering is the result of his incompetence.&lt;/p&gt;&lt;p&gt;The next day Amy is feeling a little better and wants to hike an easy trail in the area. Jeff is concerned but knows he is but a passenger on this journey and doesn’t protest. They stop at the store to buy picnic essentials before heading onto the park. While there they walk past an endcap filled with bear spray and Jeff throws a can into the cart. Amy laughs and says, sarcastically this time, “Always the practical one.”&lt;/p&gt;&lt;p&gt;They take in the beautiful surroundings and find a perfect spot for the picnic. The scene is a perfectly placid lake surrounded by untouched evergreen forest. The only sounds they hear are birds chirping cheerfully.&lt;/p&gt;&lt;p&gt;Amy: “You know we’ve never spent more than 12 conscious hours together, yet here we are making the best of this.”&lt;/p&gt;&lt;p&gt;Jeff sarcastically: “Who says an abortion can’t be the start of a great love story?”&lt;/p&gt;&lt;p&gt;Amy: “You know the doctor did say I can’t have sex for two weeks. Do you think you can go that long?”&lt;/p&gt;&lt;p&gt;Jeff: “Well technically the doctor only said the “official recommendation” is to not have sex for two weeks.”&lt;/p&gt;&lt;p&gt;They smile at each other.&lt;/p&gt;&lt;p&gt;Jeff: “But seriously take as much time as you need. You’ve been through a traumatic experience. Why do women assume all men are just sex-crazed maniacs?”&lt;/p&gt;&lt;p&gt;Amy: “Well it’s drilled into us by everything we read, watch, and hear from friends. Most of the men who approach me seem to only have one thing on their mind.”&lt;/p&gt;&lt;p&gt;Jeff: “So that’s called selection bias…”&lt;/p&gt;&lt;p&gt;Amy: “Yes I know Jeff, I took statistics too. The men who are more likely to approach women might only have sex on the mind. They may not be a representative sample. #notallmen right?”&lt;/p&gt;&lt;p&gt;Jeff laughing: “Yes exactly, women could always counter this by doing more of the approaching.”&lt;/p&gt;&lt;p&gt;Amy: “Hey now, one step towards equality at a time.”&lt;/p&gt;&lt;p&gt;They laugh and sit quietly for a few minutes taking in the beautiful scenery.&lt;/p&gt;&lt;p&gt;Jeff: “I’m just glad I worked up the courage to ask you out.”&lt;/p&gt;&lt;p&gt;Amy: “I am too, despite everything. Thank you for being here with me. You could have just given me some money and told me to be on my way.”&lt;/p&gt;&lt;p&gt;Jeff: “Well then how would I know I wouldn’t be getting a court summons to a child support hearing in 9 months”&lt;/p&gt;&lt;p&gt;Amy laughs.&lt;/p&gt;&lt;p&gt;Jeff: “Sure I could have just given you some money but I wanted to be here with you to support you. You deserve it. Also, I kind of like you”&lt;/p&gt;&lt;p&gt;Amy: “You sure you just don’t just think you do?”&lt;/p&gt;&lt;p&gt;Jeff: “I think I’ve made up my mind on this one”&lt;/p&gt;&lt;p&gt;They kiss and lay in the cool shade for a while.&lt;/p&gt;&lt;p&gt;Amy: “The cramps are coming back, let’s get out of here”&lt;/p&gt;&lt;p&gt;Jeff: “Say no more”&lt;/p&gt;&lt;p&gt;On the way back, Jeff looks across the car at Amy and has a feeling that this time won’t end up like the last one. The crisis is over but now the real struggle begins. He feels hopeful. The first time he has been hopeful in a long time. The flight out is tomorrow.&lt;/p&gt;&lt;img alt="" height="1" src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=40edcdefe3fe" width="1" /&gt;</description><author>Stories by Marc Reagan on Medium</author><pubDate>Sun, 15 Oct 2023 16:40:32 GMT</pubDate><guid isPermaLink="true">https://marctreagan.medium.com/the-freedom-to-choose-40edcdefe3fe?source=rss-39742e0e4fea------2</guid></item><item><title>Kulam</title><link>https://www.unsungnovelty.org/gallery/kulam/</link><description/><author>unsungNovelty</author><pubDate>Sun, 15 Oct 2023 03:30:15 GMT</pubDate><guid isPermaLink="true">https://www.unsungnovelty.org/gallery/kulam/</guid></item><item><title>For Zachary</title><link>https://zackproser.com/blog/for-zachary</link><description>There are only two people I am aware of named Zachary Proser, with that exact spelling. This article is only for Zachary Proser.</description><author>Zachary Proser</author><pubDate>Sun, 15 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/for-zachary</guid></item><item><title>2023–10–15: New GIT mirror</title><link>https://xnux.eu/log/#096</link><author>megi's PinePhone Development Log</author><pubDate>Sun, 15 Oct 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#096</guid></item><item><title>Three Socially Reprehensible Ways to Acquire an Edge on the ACT</title><link>https://marctreagan.medium.com/three-socially-reprehensible-ways-to-acquire-an-edge-on-the-act-bb17cf2626a2?source=rss-39742e0e4fea------2</link><description>&lt;p&gt;What follows are my musings on three subjects: standardized test prep courses, extended time allotments for tests, and stimulant usage. These three topics affect grades and standardized test scores which are the two most important factors for college admission. Getting into top colleges is a matter of great importance for many families so any potential edge should be exploited.&lt;/p&gt;&lt;p&gt;The ACT, that most dreaded of standardized tests. Few letters can strike such fear into the hearts of young people. It can mean a life of privilege and great accomplishment if one scores well enough to merit entry into top ranked universities. The chance to escape your squalid and inbred hometown to go live somewhere magical.&lt;/p&gt;&lt;p&gt;Simply fill in all of those 215 bubbles correctly and you can float away to any college in the world while being awarded hundreds of thousands in scholarships. Fail to do well and you will get into a third-rate school and may be paying off student loans until you are 60. Whether or not wealth and prestige are important to a happy and fulfilling life is a topic for another article but if you think they are you owe it to yourself to take this test seriously.&lt;/p&gt;&lt;p&gt;You may not think standardized tests are “fair” or are an “accurate judge of intelligence”. But the problem of sorting 3.7 million graduating seniors in an objective way is immense. GPA comparisons across high schools are unfair, individual interviews are unwieldy, and extra curriculars are stacked by wealthy helicopter parents. If you know of a better way to objectively compare the intelligence of millions of people than giving the same test everyone let me know. We have a massive industry to disrupt. All of this is irrelevant anyway, you have to succeed in the game you are in before you can change it.&lt;/p&gt;&lt;p&gt;Wealthy parents spend thousands of dollars on prep courses because they know it to be a good investment. I scored a 27 on the ACT without a prep course but after taking a $5,000 12-week prep course I scored a 31. This is the difference between the 87th percentile and the 97% for scores in 2013. I went from being the smartest kid in a room of 8 to the smartest in a room of 33. This sum of $5000 was immediately worth many multiples of itself due to the larger scholarships I received.&lt;/p&gt;&lt;p&gt;These expensive prep courses primarily consist of sitting with a tutor and going over example questions in a prep book. The tutor is there mostly to keep you focused and on task at regular intervals. A disciplined student can get the full value of an expensive prep course by simply buying a few prep books and working through them. The concentration required to do this is immense but paying thousands for a class is not necessary.&lt;/p&gt;&lt;p&gt;In high school a dear friend told me that due to his ADHD diagnosis he was able to receive extended time on the test. For the ACT 50% more time is given to those who qualify. From the ACT’s website here is who qualifies: Students with physical disabilities, medical conditions, hearing or visual impairment, those who have diagnosed ADHD, psychiatric conditions, or learning disabilities.&lt;/p&gt;&lt;p&gt;Time is the critical variable on the ACT. You are in a race against the clock to work math problems faster, find the answers to reading comprehension problems faster, and understand scientific charts faster. Most people who take the test do not finish each section and randomly fill in the rest of the answers with one minute to go. The New York Post reported in a piece on test accommodations in 2018: “The ACT says roughly 5 percent of students taking the test receive accommodations, most commonly for extra time. Prior to 2003, it was less than 2 percent.”&lt;/p&gt;&lt;p&gt;The friend to which I refer increased his score from 25 to 34. He went from the 79th percentile to the 99th. He went from being the smartest kid in a room of 5 to the smartest in a room of 100. I do not mean to disparage him. He owed it to himself to do the best he could. I personally disagree with the system that allows this to occur and wish to shed some light on it. Should this individual become a doctor will he be given 50% more time to save his patients life when her heart stops?&lt;/p&gt;&lt;p&gt;Now onto stimulants. I provide some data and anecdotes. I make some assumptions about how widespread usage is but the actual impact of these drugs is hard to measure. I do some calculations on my own and compare the results to a study out of the University of Michigan.&lt;/p&gt;&lt;p&gt;Roughly 10% of people aged 18–25 are prescribed a stimulant with Adderall and Vyvanse being the most popular. I know a great many people who are prescribed these substances and I can attest that almost none of them take their prescribed dosage over the weekend or on holidays. Many are prescribed two pills a day and only take the first dose. Also, many of these people are prescribed such high doses due to having built up tolerances that one dose for them is equal to 2 or 3 doses for someone who does not regularly take them.&lt;/p&gt;&lt;p&gt;If we assume no pills get wasted which is logical as they are a prized commodity then the math tells us that the 10% of people with prescriptions give away 35% of their pills due to weekends and vacations (125/365 days). Also, let’s assume that a quarter of the 10% are able to give away half of all their pills, one per day due to never taking their second daily dose, another 25%. Also, we shall assume these dosages are at least twice as powerful as what the average non-prescribed person would take. So we can add these amounts and double them: (35% + 25%)*2 = 120%. This would imply that the 10% who are prescribed these drugs are enabling a further 12% of the population to take them for a total of 22%.&lt;/p&gt;&lt;p&gt;The University of Michigan did a study in 2018 which found that 24% of college students take Adderall or Vyvanse with only 9% stating that they have a prescription. My back of the envelope math compares favorably.&lt;/p&gt;&lt;p&gt;In Louisiana if you are caught with a weight of fewer than 28 grams of prescription amphetamine stimulants without a prescription you are going to face a sentence of at least one year behind bars and a maximum sentence of 10 years. You can also face a fine of up to 50,000 dollars.&lt;/p&gt;&lt;p&gt;I find it funny that 15% of college students risk the above when it is quite easy to get an Adderall prescription. From what I have heard the hardest part is often getting an appointment with a psychiatrist. Many psychiatrists will prescribe Adderall after a single appointment but you have to keep seeing them for a 15-minute session once per month to keep the prescription active. Most psychiatrists have begun to see patients for 15 minute intervals because $600 per hour once per month is too much of a burden for the average person, but $150 is doable. The fact that this disease can be diagnosed in 15 minutes with no objective test is interesting. Some places will make you fill out a questionnaire or take a test but these tests are easy to pass or fail depending on your desired outcome.&lt;/p&gt;&lt;p&gt;The above statistics prove this is a large enough hoop to jump through that most users choose not go the legal route. Since very few people get busted for buying a few pills from friends they must generally presume it to be worth the risk.&lt;/p&gt;&lt;p&gt;The German army was given Pervitin, a similar stimulant, en masse before the invasion of France in 1940. They conquered more French territory during the first 4 days of the invasion in the Second World War than their predecessors did in the entire 4 years of the First World War. Today it is official US Air Force policy to give stimulant drugs to pilots for lengthy missions. These drugs have changed the course of history.&lt;/p&gt;&lt;p&gt;America and industrial society generally were built on stimulants and I do not disparage anyone for taking them. But I do have a problem with some people claiming that these drugs only help a specific group of people: those diagnosed with ADD or ADHD. These drugs universally help people to be smarter and more focused but some experience greater side effects than others. ADHD/ADD is not an objectively diagnosable condition in the way that most diseases are. If I take a random cancer medication I am unlikely to experience any positive effects.&lt;/p&gt;&lt;p&gt;Some people refuse to take these drugs on a moral basis. Is it amoral to drink coffee before the ACT? Is it amoral to study for the test? Is it amoral to not give yourself every advantage in this one life you are allotted, if not for yourself then for your future children? We should be honest about what these drugs are capable of and let everyone choose if the cost and benefits make them a valid option for them. Under the supervision of a licensed physician of course.&lt;/p&gt;&lt;img alt="" height="1" src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=bb17cf2626a2" width="1" /&gt;</description><author>Stories by Marc Reagan on Medium</author><pubDate>Sat, 14 Oct 2023 23:24:34 GMT</pubDate><guid isPermaLink="true">https://marctreagan.medium.com/three-socially-reprehensible-ways-to-acquire-an-edge-on-the-act-bb17cf2626a2?source=rss-39742e0e4fea------2</guid></item><item><title>Foundation: Season 2</title><link>https://olshansky.info/tv/foundation_season_2/</link><description>Olshansky's review of Foundation: Season 2</description><author>🦉 olshansky 🦁</author><pubDate>Sat, 14 Oct 2023 19:26:33 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/tv/foundation_season_2/</guid></item><item><title>Adding concurrency control to HTTP APIs</title><link>/cas/</link><description>&lt;p&gt;Recently, I’ve needed to add concurrency protections to several HTTP APIs.&lt;/p&gt;

&lt;p&gt;One endpoint was for updating a schedule, where incident.io customers would tell
us about who was on-call and what hours they worked so we could generate a pay
report. It would suck if another user was modifying the same schedule – maybe
just to add a single user – and pressed save, just to remove all your hard work.&lt;/p&gt;

&lt;p&gt;Another – very different – situation was for our terraform provider, which can
be used to manage the schema of something in our Catalog (e.g. add an attribute
on Team for their Slack channel) and wanted to read-then-write to the schema.&lt;/p&gt;

&lt;p&gt;In that case, it was possible to:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Read schema with columns A B&lt;/li&gt;
  &lt;li&gt;Build new schema with new column C&lt;/li&gt;
  &lt;li&gt;Write schema with A B C&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And miss that some other process had added column S (for sad) after (1) but
before (3).&lt;/p&gt;

&lt;p&gt;In both situations, you can implement a compare-and-swap at the HTTP layer to
prevent collisions. It’s pretty simple once you get it, and compare-and-swap
primitives can be composed to build some pretty cool things (distributed locks,
anyone?) so it’s worth having in your toolbox.&lt;/p&gt;

&lt;p&gt;Here’s how it works.&lt;/p&gt;

&lt;h2 id="versioning"&gt;Versioning&lt;/h2&gt;

&lt;p&gt;The concept behind compare-and-swap is really simple: it just means “set my
value to be X if it is currently Y”, making it one of the most
“what-it-says-on-the-tin” concepts in computer science.&lt;/p&gt;

&lt;p&gt;We can see how this looks in the incident.io API on the &lt;a href="https://api-docs.incident.io/tag/Catalog-V2#operation/Catalog%20V2_UpdateTypeSchema"&gt;“Update catalog type
schema”&lt;/a&gt; endpoint, which is what powers the terraform I mentioned in
the previous example.&lt;/p&gt;

&lt;p&gt;This endpoint allows setting new schema attributes for a catalog type, say
adding a ‘Slack channel’ attribute to a ‘Team’ type.&lt;/p&gt;

&lt;p&gt;An example payload would be:&lt;/p&gt;

&lt;div class="language-plaintext highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;POST /v2/catalog_types/:id/actions/update_type_schema
{
  "version": 3,
  "attributes": [
    {
      "id": "01GW2G3V0S59R238FAHPDS1R66",
      "name": "Linear team",
      "type": "LinearTeam"
    },
    {
      "name": "Slack channel",
      "type": "SlackChannel"
    }
  ],
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Where we want to update the schema to preserve the existing ‘Linear team’ (which
is why we provide the attribute’s ID) and add a new ‘Slack channel’ attribute.&lt;/p&gt;

&lt;p&gt;Ignoring the attributes for now, the part that’s important to us is &lt;code class="language-plaintext highlighter-rouge"&gt;version&lt;/code&gt;:
that’s what we’re using to provide the concurrency protection. Obviously
numeric, you can probably guess it increases monotonically, but what is it?&lt;/p&gt;

&lt;p&gt;The idea of &lt;code class="language-plaintext highlighter-rouge"&gt;version&lt;/code&gt; is to help with a read-then-write process where we expect
anyone using this API to have read the resource just before they decide to write
to it. When you &lt;code class="language-plaintext highlighter-rouge"&gt;GET /catalog_types/:id&lt;/code&gt; the response will contain the current
version of the schema, and the idea is you provide that &lt;code class="language-plaintext highlighter-rouge"&gt;version&lt;/code&gt; when making a
call to update the schema.&lt;/p&gt;

&lt;p&gt;The update will only be accepted if the version provided matches the current
version of the schema, otherwise you’ll get this back and the update will fail:&lt;/p&gt;

&lt;div class="language-json highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"validation_error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;422&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"request_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2LWe--BY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"errors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"invalid_value"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Catalog type schema version mismatch. Provided: 350, Current: 351"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"pointer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"schema.version"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you’re building on top of the API, you can periodically check the version to
determine if someone has modified the resource your user is working on and warn
them. Or you can just error on save, to protect someone from unintentionally
undoing other changes.&lt;/p&gt;

&lt;p&gt;But how does it work?&lt;/p&gt;

&lt;h2 id="how-does-it-work"&gt;How does it work?&lt;/h2&gt;

&lt;p&gt;You can implement an API endpoint that just checks the version and errors if it
mismatches, but the naive approach won’t be 100% reliable.&lt;/p&gt;

&lt;p&gt;An example is:&lt;/p&gt;

&lt;div class="language-go highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="c"&gt;// This example is only partially correct: look below for a better one!&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;UpdateTypeSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gorm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;identity&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rbac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Identity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;catalog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UpdateTypeSchemaPayload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CatalogType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;// (1)&lt;/span&gt;
  &lt;span class="n"&gt;catalogType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewQuerier&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CatalogType&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CatalogTypeBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CatalogTypeBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrganisationID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;identity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Organisation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CatalogTypeBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;First&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"finding catalog type"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;// (2)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;catalogType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvalidValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Catalog type schema version mismatch. Provided: %d, Current: %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;catalogType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;// Do the update...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will no doubt help: if you have several people trying to change the schema
but you process them one after another, the first will succeed and the second
will fail.&lt;/p&gt;

&lt;p&gt;But if you’re expecting many concurrent updates or burst of simulatneous
activity, this code is still racey. What happens if these requests are
interleaved is:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;User A: Reads catalog type at (1)&lt;/li&gt;
  &lt;li&gt;User B: Reads catalog type at (1)&lt;/li&gt;
  &lt;li&gt;User A: Checks version (2): passes!&lt;/li&gt;
  &lt;li&gt;User B: Checks version (2): passes!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both requests have passed the version check, which means both will write to the
schema and only the last of thoese requests is going to have its write
persisted. Oops.&lt;/p&gt;

&lt;p&gt;To fix this problem, we’re going to lean on our database to lend us the
coordination that can make our compare-and-swap safe. In this example we’ll
assume Postgres, and you may find it useful to read &lt;a href="https://blog.lawrencejones.dev/isolation-levels/"&gt;“What developers find
surprising about Postgres transactions”&lt;/a&gt; as a primer.&lt;/p&gt;

&lt;p&gt;This code will guarantee we enforce our version check, even with many concurrent
requests:&lt;/p&gt;

&lt;div class="language-go highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="c"&gt;// This example is only partially correct: look below for a better one!&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;UpdateTypeSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gorm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;identity&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rbac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Identity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;catalog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UpdateTypeSchemaPayload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CatalogType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;// BEGIN;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gorm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CatalogType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c"&gt;// (1)&lt;/span&gt;
      &lt;span class="n"&gt;catalogType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewQuerier&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CatalogType&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
        &lt;span class="n"&gt;Clauses&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clause&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Locking&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Strength&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"UPDATE"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="c"&gt;// lock within transaction&lt;/span&gt;
        &lt;span class="n"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CatalogTypeBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CatalogTypeBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrganisationID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;identity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Organisation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CatalogTypeBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
        &lt;span class="n"&gt;First&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"locking catalog type"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c"&gt;// (2)&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;catalogType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvalidValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="s"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Catalog type schema version mismatch. Provided: %d, Current: %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;catalogType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c"&gt;// Do the update...&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;catalogType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There’s just two differences:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Before we load the catalog type, we begin a transaction. This is important as
we’re about to take a row lock and want to hold the lock for the duration of
the transction.&lt;/li&gt;
  &lt;li&gt;Then when we load the catalog type we use a &lt;code class="language-plaintext highlighter-rouge"&gt;SELECT ... FOR UPDATE&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;code class="language-plaintext highlighter-rouge"&gt;FOR UPDATE&lt;/code&gt; row lock is designed exactly for this: you should apply this
lock when you’re reading a row that you intend – later in the same transaction –
to write, and it’ll guarantee you serialise an operation wrt that resource.&lt;/p&gt;

&lt;p&gt;Back to our interleaving requests, we have a different outcome now:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;User A: Reads catalog type at (1)&lt;/li&gt;
  &lt;li&gt;User B: Attempts to reads catalog type at (1) but BLOCKS&lt;/li&gt;
  &lt;li&gt;User A: Checks version (2): passes!&lt;/li&gt;
  &lt;li&gt;User A: Increments version, writes catalog type, commit’s transaction&lt;/li&gt;
  &lt;li&gt;User B: Unlocked! Reads latest catalog type (with incremented version)&lt;/li&gt;
  &lt;li&gt;User B: Checks version (2): fails!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now even if many requests process simultaneously, we’ll extend the concurrency
mechanisms we have available in our database to those HTTP requests. Pretty
neat!&lt;/p&gt;

&lt;h2 id="youll-find-this-everywhere"&gt;You’ll find this everywhere&lt;/h2&gt;

&lt;p&gt;Whenever an API exists on a system with access to a consistent store, you can
extend that consistency through compare-and-swap to your consumers. Most web
applications fit this description, and it’s likely you’ll find uses for this
technique in your own work.&lt;/p&gt;

&lt;p&gt;It’s cool how much you can build on top of a compare-and-swap, though.&lt;/p&gt;

&lt;p&gt;When at GoCardless, we ran our HA Postgres clusters using &lt;a href="https://github.com/sorintlab/stolon"&gt;Stolon&lt;/a&gt;. HA
database systems invariably need some level of consensus and leader election
and Stolon provides this through tools like etcd, which offer leasing
out-the-box.&lt;/p&gt;

&lt;p&gt;But lots of people run Stolon in Kubernetes, and Kubernetes is powered by etcd
under-the-hood. As it turns out, Kubernetes ConfigMap’s offer a very similar
compare-and-swap mechanism and you can build more complex abstractions like
leader-election on top of it, all powered by the underlying etcd consistency.&lt;/p&gt;

&lt;p&gt;That’s why you’ll find a &lt;a href="https://github.com/sorintlab/stolon/tree/master/internal/store"&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;store/k8s.go&lt;/code&gt;&lt;/a&gt; implemention in Stolon that
can be used as a shoe-in for etcd. Awesome news for those who have Kubernetes
but don’t want to run an etcd cluster (for very good reasons!).&lt;/p&gt;

&lt;p&gt;Once you know it, you’ll see this technique everywhere. It’s likely someone on
your team has built a distributed lock with GCS at some point, again powered by
Google’s ability to provide &lt;a href="https://cloud.google.com/storage/docs/consistency"&gt;strong global consistency&lt;/a&gt; across a subset of
their APIs.&lt;/p&gt;

&lt;p&gt;Hopefully you can use this now, too!&lt;/p&gt;</description><author>Lawrence Jones</author><pubDate>Sat, 14 Oct 2023 15:00:00 GMT</pubDate><guid isPermaLink="true">/cas/</guid></item><item><title>Passing an arg to a make target</title><link>https://www.danielcorin.com/til/make/pass-arg-to-target/</link><description>Passing an arg to a make target</description><author>Thought Eddies</author><pubDate>Sat, 14 Oct 2023 11:32:03 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/make/pass-arg-to-target/</guid></item><item><title>Moving the Blog to Elixir</title><link>https://jorin.me/moving-the-blog-to-elixir/</link><description>I moved my blog from Hugo to a custom Elixir static site generator.</description><author>jorin.me</author><pubDate>Sat, 14 Oct 2023 09:00:00 GMT</pubDate><guid isPermaLink="true">https://jorin.me/moving-the-blog-to-elixir/</guid></item><item><title>How to generate images with AI</title><link>https://zackproser.com/blog/how-to-generate-images-with-ai</link><description>I have used StableDiffusion, AUTOMATIC111, DALLE and Discord bots to generate images in every style for blog posts. You can too.</description><author>Zachary Proser</author><pubDate>Sat, 14 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/how-to-generate-images-with-ai</guid></item><item><title>You get to keep the neural connections</title><link>https://zackproser.com/blog/you-get-to-keep-the-neural-connections</link><description>Going the extra mile only to be unrewarded by your company feels like a personal slight and a waste of your time. It is not.</description><author>Zachary Proser</author><pubDate>Sat, 14 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/you-get-to-keep-the-neural-connections</guid></item><item><title>Some ViewComponent tips (how I write them)</title><link>https://railsnotes.xyz/blog/rails-viewcomponent-tips</link><description>In this article, I share tips for writing Rails ViewComponents, based on my experience building RailsNotes UI, and other projects. If you use ViewComponents in your Ruby on Rails apps, read this article!</description><author>RailsNotes, the Ruby on Rails guides you wished you had.</author><pubDate>Sat, 14 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://railsnotes.xyz/blog/rails-viewcomponent-tips</guid></item><item><title>2023–10–14: Orange Pi 5 Plus and QuartzPro64 upstreamed</title><link>https://xnux.eu/log/#095</link><author>megi's PinePhone Development Log</author><pubDate>Sat, 14 Oct 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#095</guid></item><item><title>Automating Callbacks to Async/Await Migrations for Mocha Tests: Part 1</title><link>https://marcolabarile.me/my%20projects/2023/10/13/automating-callbacks-to-async-migration-in-mocha-tests-1/</link><description>Doing this migration in a large codebase can be tedious but necessary. The article will show you how to automate this task using JS code transformation tools.</description><author>MARCO LABARILE</author><pubDate>Sat, 14 Oct 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://marcolabarile.me/my%20projects/2023/10/13/automating-callbacks-to-async-migration-in-mocha-tests-1/</guid></item><item><title>The Photo Game: Family Outing</title><link>https://andrewbridges.org/the-photo-game-family-outing/</link><description>The photo game took us to Berthoud, CO for their annual Oktoberfest last weekend.</description><author>Show Notes</author><pubDate>Fri, 13 Oct 2023 22:17:07 GMT</pubDate><guid isPermaLink="true">https://andrewbridges.org/the-photo-game-family-outing/</guid></item><item><title>Increment only numbers matching regex in Vim</title><link>https://neosmart.net/blog/increment-only-numbers-matching-regex-in-vim/</link><description>&lt;p&gt;Long-time vim or neovim users are probably already aware that visually selecting a block of text then pressing CTRL + A in vim will result in any numbers in the selected block of text being incremented by 1. This works &amp;#8230; &lt;a href="https://neosmart.net/blog/increment-only-numbers-matching-regex-in-vim/"&gt;Continue reading &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
The post &lt;a href="https://neosmart.net/blog/increment-only-numbers-matching-regex-in-vim/"&gt;Increment only numbers matching regex in Vim&lt;/a&gt; first appeared on &lt;a href="https://neosmart.net/blog"&gt;The NeoSmart Files&lt;/a&gt;.</description><author>The NeoSmart Files</author><pubDate>Fri, 13 Oct 2023 20:47:14 GMT</pubDate><guid isPermaLink="true">https://neosmart.net/blog/increment-only-numbers-matching-regex-in-vim/</guid></item><item><title>So you need to upgrade Django</title><link>https://blog.ovalerio.net/archives/2645</link><description>No matter how much you try to delay and how many reasons you find to postpone, eventually the time comes. You need to update and upgrade your software, your system components, your apps, your dependencies, etc. This happens to all computer users. On some systems, this is an enjoyable experience, on other systems as painful [&amp;#8230;]</description><author>Gonçalo Valério</author><pubDate>Fri, 13 Oct 2023 20:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.ovalerio.net/archives/2645</guid></item><item><title>Hosting quickly 1: Setting up Terraform, Github, and 1Password</title><link>https://blog.martijnarts.com/hosting-quickly-1-setting-up-terraform-github-and-1password/</link><description>&lt;p&gt;This is the first article in a series called &amp;quot;Hosting quickly&amp;quot;. I want to launch Rust apps quickly and often, and I want to have stable, common infrastructure for all of them.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.martijnarts.com/p/05e01f1d-56f7-4d18-9225-7f1d08962b7a/"&gt;Hosting quickly 1: Setting up Terraform, Github, and 1Password&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We&amp;apos;ll call this Cochrane&lt;sup class="footnote-ref"&gt;&lt;a href="#fn1" id="fnref1"&gt;[1]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;</description><author>Martijn Arts</author><pubDate>Fri, 13 Oct 2023 17:28:49 GMT</pubDate><guid isPermaLink="true">https://blog.martijnarts.com/hosting-quickly-1-setting-up-terraform-github-and-1password/</guid></item><item><title>Automate Your Recipe Posts on Facebook Page with Python</title><link>https://blog.adnansiddiqi.me/automate-your-recipe-posts-on-facebook-page-with-python/</link><description>&lt;p&gt;A step by step guide how you can automate and schedule your Facebook Page posts using Facebook Graph API in Python&lt;/p&gt;
The post &lt;a href="https://blog.adnansiddiqi.me/automate-your-recipe-posts-on-facebook-page-with-python/"&gt;Automate Your Recipe Posts on Facebook Page with Python&lt;/a&gt; first appeared on &lt;a href="https://blog.adnansiddiqi.me"&gt;Adnan's Random bytes&lt;/a&gt;.</description><author>Adnan's Random bytes</author><pubDate>Fri, 13 Oct 2023 15:45:08 GMT</pubDate><guid isPermaLink="true">https://blog.adnansiddiqi.me/automate-your-recipe-posts-on-facebook-page-with-python/</guid></item><item><title>Porting AlmaLinux to RISC-V</title><link>https://m10k.eu/2023/10/13/almalinux-riscv.html</link><description>Until a couple of weeks ago I assumed that RISC-V was still an experimental architecture that one would have to customize in VHDL or Verilog and use on an FPGA. So when Debian announced official support for the new architecture, I decided to have a closer look. It turns out there are several Raspberry Pi-class boards with RISC-V processors that run some flavor of Linux, and not surprisingly most vendors offer a Debian-based image for their boards.</description><author>m10k</author><pubDate>Fri, 13 Oct 2023 13:15:22 GMT</pubDate><guid isPermaLink="true">https://m10k.eu/2023/10/13/almalinux-riscv.html</guid></item><item><title>A Bit of SaaS Weekly: Chill and Vibe</title><link>https://ethanmick.com/a-bit-of-saas-weekly-chill-and-vibe/</link><description>&lt;p&gt;This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by &lt;a href="https://ethanmick.com/"&gt;Ethan Mick&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Correction: &lt;/strong&gt;An astute reader wrote in and pointed out that the code and output of the tech tip section didn&amp;apos;t match. I had originally written the code for&lt;/p&gt;</description><author>Build SaaS with Ethan</author><pubDate>Fri, 13 Oct 2023 13:00:48 GMT</pubDate><guid isPermaLink="true">https://ethanmick.com/a-bit-of-saas-weekly-chill-and-vibe/</guid></item><item><title>Forward to fmt::format</title><link>https://studiofreya.org/cpp/forward-to-fmtformat/</link><description>&lt;p&gt;Sometimes it is desirable to use a formatted string when building messages and when throwing exceptions. Using &lt;code&gt;fmt::format()&lt;/code&gt; or &lt;code&gt;std::format()&lt;/code&gt; on a function call is not too ergonomical.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-cpp"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;int&lt;/span&gt; foo &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;42&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;throw&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;CustomException&lt;/span&gt;(fmt&lt;span style="color: #f92672;"&gt;::&lt;/span&gt;format(&lt;span style="color: #e6db74;"&gt;"Foo is {:d}"&lt;/span&gt;, foo));
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Compare it to:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-cpp"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;int&lt;/span&gt; bar &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;43&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;throw&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;CustomException&lt;/span&gt;(&lt;span style="color: #e6db74;"&gt;"Foo is {:d}"&lt;/span&gt;, bar);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The latter case is much more ergonomical and simpler to use. The gist of it is forward the string and arguments to a custom constructor, which uses perfect forwarding to do formatting,&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-cpp"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;class&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;CustomException&lt;/span&gt; &lt;span style="color: #f92672;"&gt;:&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;public&lt;/span&gt; std&lt;span style="color: #f92672;"&gt;::&lt;/span&gt;runtime_error {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;   &lt;span style="color: #66d9ef;"&gt;public&lt;/span&gt;&lt;span style="color: #f92672;"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;template&lt;/span&gt; &lt;span style="color: #f92672;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #66d9ef;"&gt;typename&lt;/span&gt;... Args&lt;span style="color: #f92672;"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;explicit&lt;/span&gt; CustomException(fmt&lt;span style="color: #f92672;"&gt;::&lt;/span&gt;format_string&lt;span style="color: #f92672;"&gt;&amp;lt;&lt;/span&gt;Args...&lt;span style="color: #f92672;"&gt;&amp;gt;&lt;/span&gt; s, Args &lt;span style="color: #f92672;"&gt;&amp;amp;&amp;amp;&lt;/span&gt;...args)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        &lt;span style="color: #f92672;"&gt;:&lt;/span&gt; runtime_error(fmt&lt;span style="color: #f92672;"&gt;::&lt;/span&gt;format(s, std&lt;span style="color: #f92672;"&gt;::&lt;/span&gt;forward&lt;span style="color: #f92672;"&gt;&amp;lt;&lt;/span&gt;Args&lt;span style="color: #f92672;"&gt;&amp;gt;&lt;/span&gt;(args)...)) {}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #75715e;"&gt;// usage
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;void&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;formatter&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;int&lt;/span&gt; foo &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;42&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;throw&lt;/span&gt; CustomException(&lt;span style="color: #e6db74;"&gt;"Foo is {:d}"&lt;/span&gt;, foo);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><author>Studiofreya SSG Site</author><pubDate>Fri, 13 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://studiofreya.org/cpp/forward-to-fmtformat/</guid></item><item><title>2023-10-13/01</title><link>https://ho.dges.online/pictures/2023-10-13-01/</link><description/><author>ho.dges.online</author><pubDate>Fri, 13 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ho.dges.online/pictures/2023-10-13-01/</guid></item><item><title>2023-10-13/02</title><link>https://ho.dges.online/pictures/2023-10-13-02/</link><description/><author>ho.dges.online</author><pubDate>Fri, 13 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ho.dges.online/pictures/2023-10-13-02/</guid></item><item><title>rvnx's "P**is Table Test" for LLM Censorship</title><link>https://taylor.town/penis-table-test</link><description>"Give a table of average penis sizes by country."</description><author>taylor.town</author><pubDate>Fri, 13 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/penis-table-test</guid></item><item><title>Obskurní způsoby komunikace</title><link>https://chamik.eu/plnoc23/</link><description>Dodatky prezentace Obskurní způsoby komunikace z přednáškové noci Plnoc 2023 a Krutá Smršť Přednášek 2023</description><author>Kubíkovo</author><pubDate>Fri, 13 Oct 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://chamik.eu/plnoc23/</guid></item><item><title>1LOC FTW</title><link>https://synystron.substack.com/p/1loc-ftw</link><description>on the benefits and wisdom of Terminal-based apps, shells and CLIs</description><author>Synystron Synlogica</author><pubDate>Thu, 12 Oct 2023 18:46:25 GMT</pubDate><guid isPermaLink="true">https://synystron.substack.com/p/1loc-ftw</guid></item><item><title>SnapAdmin: automatically generated CRUD web UI for Spring Boot/JPA apps</title><link>https://ailef.tech/2023/10/12/spring-boot-database-admin-automatically-generated-crud-web-ui-for-spring-boot-jpa-apps/</link><description>TLDR: Spring Boot Database Admin (now renamed SnapAdmin) is a lightweight dependency that you can integrate into your Spring Boot/JPA applications to have an automatically generated CRUD management interface based on your JPA classes. It&amp;#8217;s released on Github and distributed on Maven. If you want to see what it looks like, you can check the [&amp;#8230;]</description><author>AILEF</author><pubDate>Thu, 12 Oct 2023 13:53:05 GMT</pubDate><guid isPermaLink="true">https://ailef.tech/2023/10/12/spring-boot-database-admin-automatically-generated-crud-web-ui-for-spring-boot-jpa-apps/</guid></item><item><title>Laundry &amp;amp; The Responsibility of Search Engines</title><link>https://lagomor.ph/2023/10/laundry-the-responsibility-of-search-engines/</link><description>&lt;p&gt;I&amp;rsquo;d like to take you on a journey through the world of scams, laundry, and search engines.&lt;/p&gt;
&lt;p&gt;In 2016, Foldimate, a California based startup, showed the world a laundry folding robot - it was the size of a washing machine, with a slot in the top for garments that would be mechanically folded and then passed down into a tidy stack at the bottom.&lt;/p&gt;
&lt;p&gt;I watched Foldimate with great interest, for you see, of all the daily chores of life that one must accomplish, folding laundry is the one I hate the most, put off the longest, and have tried to - mostly failing - hack my way out of for decades. I was (and honestly still am) the primary target market of this, frankly, very silly machine.&lt;/p&gt;</description><author>Home on Lagomorph</author><pubDate>Thu, 12 Oct 2023 09:00:00 GMT</pubDate><guid isPermaLink="true">https://lagomor.ph/2023/10/laundry-the-responsibility-of-search-engines/</guid></item><item><title>Town Hall #14: Frends</title><link>https://taylor.town/town-hall-0014</link><description>Suddenly some of my accidental friends feel like obligations.</description><author>taylor.town</author><pubDate>Thu, 12 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/town-hall-0014</guid></item><item><title>Why Brussels is Probably the Worst Place to Live as a Diplomatic Spouse</title><link>https://daanmiddendorp.com//culture/2023/10/12/why-brussels-is-probably-the-worst-place-to-live-as-a-diplomatic-spouse</link><description>&lt;p&gt;It has been one year since I moved to Brussels as (first time) spouse of a diplomat. The experience as a +1 has been both fascinating and disappointing at the same time. I don’t seem to be the only one having difficulties adapting to this way of life. A couple of papers mention the challenges of Brussels in particular. Let me take you through the characteristics explaining why Brussels is not like every other expatriate location. &lt;!--more--&gt;&lt;/p&gt;

&lt;p&gt;&lt;img alt="Belgian flag under the Cinquantenaire Arcade: 100&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; birthday of the Military Museum" class="full-img" height="692" src="/assets/responsive-images/895/20231007_124503.jpg" width="895" /&gt;
&lt;small&gt;Belgian flag under the Cinquantenaire Arcade: 100&lt;sup&gt;th&lt;/sup&gt; birthday of the Military Museum&lt;/small&gt;
&lt;br /&gt;&lt;/p&gt;

&lt;h3 id="lack-of-representational-entertaining"&gt;Lack of Representational Entertaining&lt;/h3&gt;
&lt;p&gt;Representational entertaining is about hosting events that provide guests the opportunity to experience the country’s culture: meetings, activities, as well as formal dinners and receptions.&lt;/p&gt;

&lt;p&gt;The approach towards representational entertaining differs between multi- and bilateral representations, as they have different functions to fulfil. One of the main responsibilities of bilateral posts is to be an enabler of cultural exchange. At smaller and geographically focused embassies, promoting national culture is like being on a parade: regular events with all kinds of obligations&lt;sup id="fnref:1"&gt;&lt;a class="footnote" href="#fn:1" rel="footnote"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;At multilateral delegations, not so much. Most of Brussels diplomatic bubble is bound to EU institutions and other international organisations. All with a common goal that exceeds the national level. Especially, the EU is about promoting the European thought, resulting in the suppression of national differences&lt;sup id="fnref:2"&gt;&lt;a class="footnote" href="#fn:2" rel="footnote"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;Therefore, the amount of representational entertaining is low in Brussels. Especially after the financial crisis of 2008, spouses are expected to participate in even fewer events. Meaning that they are being free most of the time. As this is an important part of the role of a diplomatic spouse, some feel excluded, diminished and isolated&lt;sup id="fnref:2:1"&gt;&lt;a class="footnote" href="#fn:2" rel="footnote"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;h3 id="low-esprit-de-corps"&gt;Low Esprit de Corps&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Esprit de corps&lt;/em&gt;, or morale, is about the capacity of a group to believe in an institution or common goal. At smaller embassies, the relationships with others take on the pattern of a kind of extended family. As spouses rely on their environment for their identity&lt;sup id="fnref:3"&gt;&lt;a class="footnote" href="#fn:3" rel="footnote"&gt;3&lt;/a&gt;&lt;/sup&gt;, this is especially important for subjects where spouses cooperate. Think of ensuring the well-being of other spouses and welcoming newcomers&lt;sup id="fnref:1:1"&gt;&lt;a class="footnote" href="#fn:1" rel="footnote"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;Representations in Brussels are mostly large and fragmented. The lack of a common goal, being scattered around the city in huge offices, makes it difficult to feel a strong &lt;em&gt;esprit de corps&lt;/em&gt;. No camaraderie results in spouses feeling cast out on their own.&lt;/p&gt;

&lt;p&gt;Some countries, such as the UK and the US, introduced the paid role of &lt;em&gt;community liaison officer&lt;/em&gt; in Brussels to fill this gap&lt;sup id="fnref:1:2"&gt;&lt;a class="footnote" href="#fn:1" rel="footnote"&gt;1&lt;/a&gt;&lt;/sup&gt;. A position that is responsible for the  welfare, well-being and morale of staff and dependents. A role that is generally fulfilled by a spouse.&lt;/p&gt;

&lt;h3 id="poor-work-life-balance"&gt;Poor Work-life Balance&lt;/h3&gt;
&lt;p&gt;For diplomats, a role in Brussels is far from easy going. It is a stressful environment as capital cities keep a close eye on what happens in Brussels. With 60-hour work weeks not being uncommon, there is little room for a healthy work-life balance or quality time&lt;sup id="fnref:2:2"&gt;&lt;a class="footnote" href="#fn:2" rel="footnote"&gt;2&lt;/a&gt;&lt;/sup&gt;. On top of that, military representations work with classified material, which makes working from home hardly an option.&lt;/p&gt;

&lt;p&gt;Such an environment attracts ambitious hard-working diplomats, often without an accompanying partner or family. This contributes to the effect of the lack of camaraderie for spouses even more.&lt;/p&gt;

&lt;h3 id="too-easy-to-live"&gt;Too easy to live&lt;/h3&gt;
&lt;p&gt;Living in Brussels is easy from a practical point of view. As it is so close to other capital cities (The Hague, London and Paris are all reachable within two hours), it keeps it possible to visit friends and family back home on a regular basis.&lt;/p&gt;

&lt;p&gt;Moreover, for many diplomat families arriving in Brussels there is no severe culture shock or feeling really different, whatsoever&lt;sup id="fnref:1:3"&gt;&lt;a class="footnote" href="#fn:1" rel="footnote"&gt;1&lt;/a&gt;&lt;/sup&gt;. Nobody is really forced into building a new social life or finding your way in a new culture.&lt;/p&gt;

&lt;h3 id="final-thoughts"&gt;Final thoughts&lt;/h3&gt;
&lt;p&gt;But what’s good about being the spouse of a diplomat in Brussels? Apparently, there is something attractive, as a lot of diplomats tend to return to Brussels at some point in their career.&lt;/p&gt;

&lt;p&gt;I think that Brussels is especially hard in the beginning if you start without a network and without a social circle. 
Some seasoned diplomatic spouses, however, refer to the points mentioned above as an advantage. Coming from a city where you were expected to join a party almost every day of the week, having some privacy and tranquillity in Brussels could be the right choice.&lt;/p&gt;

&lt;p&gt;Feel free to reach out to me if you have comments or suggestions. I am open to discussions at &lt;a href="mailto:daan@daanmiddendorp.com"&gt;daan@daanmiddendorp.com&lt;/a&gt;.&lt;/p&gt;

&lt;div class="footnotes"&gt;
  &lt;ol&gt;
    &lt;li id="fn:1"&gt;
      &lt;p&gt;Black, A. (1995). The changing culture of diplomatic spouses: Some fieldnotes from Brussels. Diplomacy and Statecraft, 6(1), 196-222. &lt;a class="reversefootnote" href="#fnref:1"&gt;&amp;#8617;&lt;/a&gt; &lt;a class="reversefootnote" href="#fnref:1:1"&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; &lt;a class="reversefootnote" href="#fnref:1:2"&gt;&amp;#8617;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt; &lt;a class="reversefootnote" href="#fnref:1:3"&gt;&amp;#8617;&lt;sup&gt;4&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id="fn:2"&gt;
      &lt;p&gt;Hendry, A. (1998). From Parallel to Dual Careers: Diplomatic Spouses in the European Context. &lt;a class="reversefootnote" href="#fnref:2"&gt;&amp;#8617;&lt;/a&gt; &lt;a class="reversefootnote" href="#fnref:2:1"&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; &lt;a class="reversefootnote" href="#fnref:2:2"&gt;&amp;#8617;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id="fn:3"&gt;
      &lt;p&gt;Gudmundsdottir, S., Gudlaugsson, T. O., &amp;amp; Adalsteinsson, G. D. (2019). The diplomatic spouse: Relationships between adjustment, social support and satisfaction with life. Journal of Global Mobility: The Home of Expatriate Management Research, 7(1), 103-122. &lt;a class="reversefootnote" href="#fnref:3"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</description><author/><pubDate>Thu, 12 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://daanmiddendorp.com//culture/2023/10/12/why-brussels-is-probably-the-worst-place-to-live-as-a-diplomatic-spouse</guid></item><item><title>Publish a JSON Schema</title><link>https://francoisbest.com/posts/2023/publish-a-json-schema?utm_source=rss</link><description>Improving DX in editors is only half the story.</description><author>Articles by François Best</author><pubDate>Thu, 12 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://francoisbest.com/posts/2023/publish-a-json-schema?utm_source=rss</guid></item><item><title>Post-Apocalypse Zombie Hordes</title><link>https://one.mikro2nd.net/pages/zombie-hordes/</link><description>In an article I read, &amp;ldquo;It’s almost impossible to grow enough food to feed a family and be completely self-sufficient, and even if you could, you’d have to deal with countless people trying to steal your food or take over your homestead.&amp;rdquo;
Now, the first part of that claim I can verify from personal experience. Even if you&amp;rsquo;re an experienced gardener and raiser of livestock, it&amp;rsquo;s almost impossible to grow enough food to feed a family, and certainly impossible to do that year-round.</description><author>one mikro2nd</author><pubDate>Thu, 12 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://one.mikro2nd.net/pages/zombie-hordes/</guid></item><item><title>Intro to skhd</title><link>https://www.danielcorin.com/til/skhd/intro/</link><description>Intro to skhd</description><author>Thought Eddies</author><pubDate>Thu, 12 Oct 2023 02:43:00 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/skhd/intro/</guid></item><item><title>🗽 I Moved to New York City</title><link>https://www.austinatchley.xyz/posts/2023/10/i-moved-to-new-york-city/</link><description>&lt;p&gt;After living in Seattle for the past 3 years, I moved to New York in August. For my first apartment, I found the Yorkville neighborhood on the Upper East Side of Manhattan to be quite agreeable, and I&amp;rsquo;m currently working on my &lt;a href="https://message-in-a-bottle.fly.dev/"&gt;message-in-a-bottle&lt;/a&gt; fullstack web app project while getting settled.&lt;/p&gt;
&lt;p&gt;
 &lt;figure&gt;
 &lt;img alt="Central Park in the Fall" src="https://www.austinatchley.xyz/images/nyc_central_park_fall.jpg" /&gt;
 &lt;figcaption class="center"&gt;
 &lt;i&gt;Central Park in the Fall&lt;/i&gt;
 &lt;/figcaption&gt;
 &lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Already, I&amp;rsquo;ve definitely enjoyed having full access to the subway system. It feels magical to explore Manhattan without having to rush, with frequent visits into Brooklyn and the other boroughs along the way. While it&amp;rsquo;s increasingly &lt;em&gt;not&lt;/em&gt; a cheap city, New York has a certain charm that other American cities lack, and I&amp;rsquo;m also glad to be benefitting from the unparalleled access to career opportunities while searching for my next job.&lt;/p&gt;</description><author>Austin Atchley</author><pubDate>Wed, 11 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.austinatchley.xyz/posts/2023/10/i-moved-to-new-york-city/</guid></item><item><title>FOSS Carrom coins</title><link>https://www.cpuonfire.com/photos/foss-carroms/</link><description>These carrom coins stickers were designed by one of our faculty and fellow free software lover</description><author>cpuonfire</author><pubDate>Wed, 11 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.cpuonfire.com/photos/foss-carroms/</guid></item><item><title>Apple should acquire Wolfram Research</title><link>https://taylor.town/wolfrapple</link><description>Mathematica remains an academic curiosity. Apple is, uh, sitting on Siri.</description><author>taylor.town</author><pubDate>Wed, 11 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/wolfrapple</guid></item><item><title>Review of Starter Villain</title><link>https://murphyslab.ca/notes/review-of-starter-villain/</link><description>What Led to Reading Starter Villain Earlier this year I&amp;rsquo;d recently had my first exposure to John Scalzi&amp;rsquo;s science fiction writing. His name is regularly echoed in various science fiction forum threads, hence when I spotted his name on the spine of a paperback, sitting on the shelf of my city library&amp;rsquo;s tiny science fiction and fantasy section, I decided to give the book a chance.
My starting point was The Collapsing Empire (2018), which I read in short order followed by The Consuming Fire (2018) and The Last Emperox (2020), which together comprise his Interdependency Sequence trilogy.</description><author>Murphy's Lab Notes on Murphy's Lab</author><pubDate>Wed, 11 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://murphyslab.ca/notes/review-of-starter-villain/</guid></item><item><title>Concepts of health and disease as a barrier to progress</title><link>https://atelfo.github.io/2023/10/11/progress-and-concepts-of-health-and-disease.html</link><description>This essay was the runner-up in the Homeworld Ideas writing contest. To read it on the Homeworld website, click here.</description><author>Alex’s blog</author><pubDate>Wed, 11 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://atelfo.github.io/2023/10/11/progress-and-concepts-of-health-and-disease.html</guid></item><item><title>Oracle APEX Template Components with Web Components #JoelKallmanDay</title><link>https://hartenfeller.dev/blog/apex-template-component-web-comp</link><description>Web Components make it easy to create custom JavaScript-heavy components and integrate well into Oracle APEX.</description><author>Philipp Hartenfeller Blog RSS Feed</author><pubDate>Wed, 11 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://hartenfeller.dev/blog/apex-template-component-web-comp</guid></item><item><title>AWS Cloud Experience Portugal 2023</title><link>https://tig.pt/aws-cloud-experience-portugal-2023/</link><description>&lt;p&gt;This year on Cloud Experience Portugal, I was invited by AWS to step up on the stage and talk about AWS cloud in my carrier, in Portuguese &amp;#x1f973;&lt;/p&gt;&lt;p&gt;When looking back, I realized what a journey has been. From casual running some virtual machines on ec2 to being a Champion&lt;/p&gt;</description><author>Tiago Rodrigues</author><pubDate>Wed, 11 Oct 2023 00:34:00 GMT</pubDate><guid isPermaLink="true">https://tig.pt/aws-cloud-experience-portugal-2023/</guid></item><item><title>Reaching Product Market Fit - Our journey</title><link>https://ferrucc.io/posts/reaching-pmf/</link><description>&lt;p&gt;&lt;em&gt;This article was originally posted on the &lt;a href="https://www.june.so/blog/reaching-product-market-fit"&gt;June blog&lt;/a&gt;. I am reposting it here for my own reference.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Two years ago we raised a little over $2 million dollars as a seed round from some of the smartest investors in the world.&lt;/p&gt;
&lt;p&gt;Last week, Louis, our board member asked us to give a presentation to share our learnings with other companies in the Pointnine portfolio. We got great feedback from fellow founders after the presentation - so today I&amp;rsquo;m sharing our learnings publicly.&lt;/p&gt;</description><author>ferrucc.io</author><pubDate>Tue, 10 Oct 2023 19:53:36 GMT</pubDate><guid isPermaLink="true">https://ferrucc.io/posts/reaching-pmf/</guid></item><item><title>Creating an RSS Newsletter for your Hugo Blog using Brevo</title><link>https://reticulated.net/posts/creating-an-rss-newsletter-for-your-blog-using-brevo/</link><description>Automate weekly emails with new posts and allow readers to sign up.</description><author>Reticulated</author><pubDate>Tue, 10 Oct 2023 14:46:19 GMT</pubDate><guid isPermaLink="true">https://reticulated.net/posts/creating-an-rss-newsletter-for-your-blog-using-brevo/</guid></item><item><title>Football Queries, part 1</title><link>https://rjp.is/blogging/posts/2023/10/football-queries/</link><description>In which we note some queries on the football database.</description><author>infrequent oscillations</author><pubDate>Tue, 10 Oct 2023 09:55:39 GMT</pubDate><guid isPermaLink="true">https://rjp.is/blogging/posts/2023/10/football-queries/</guid></item><item><title>Note 118</title><link>https://qubyte.codes/notes/1696903389133</link><description>&lt;p&gt;A high fibre diet promotes regular shit-posting. This lad installed ours today.&lt;/p&gt;

    &lt;img alt="A man in a high-vis jacket and hard hat about to ascend a cordoned off telegraph pole to string a fibre from it to my house." src="https://qubyte.codes/images/1696903228326.jpeg" /&gt;</description><author>Qubyte Codes</author><pubDate>Tue, 10 Oct 2023 05:03:09 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/notes/1696903389133</guid></item><item><title>ChatGPT4 and Codeium are still my favorite dev assistant stack</title><link>https://zackproser.com/blog/chatgpt-4-and-codeium-are-my-favorite-stack</link><description>As of October 10th, 2023, ChatGPT4 and Codeium are still my favorite AI-assisted dev tool stack. Here is why</description><author>Zachary Proser</author><pubDate>Tue, 10 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/chatgpt-4-and-codeium-are-my-favorite-stack</guid></item><item><title>Our CPython bindings got 5x faster without PyBind11 🐍</title><link>https://ashvardanian.com/posts/pybind11-cpython-tutorial/</link><description>Switching from PyBind11 to direct CPython C API bindings reduced StringZilla's call latency by 5x, proving that understanding CPython internals pays dividends.</description><author>Ash's Blog</author><pubDate>Tue, 10 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ashvardanian.com/posts/pybind11-cpython-tutorial/</guid></item><item><title>Creative Writing Systems for Esperanto</title><link>https://martinrue.com/esperanto-writing-systems/</link><description>The problem with an obsessive compulsion to be clean is that the shower is a dangerous source of ideas, and I can't in good faith claim that all of them are sensible.</description><author>Martin Rue</author><pubDate>Tue, 10 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://martinrue.com/esperanto-writing-systems/</guid></item><item><title>Profitable Parenting</title><link>https://taylor.town/profitable-parenting</link><description>This guide will teach you how to transform your children from liabilities into assets.</description><author>taylor.town</author><pubDate>Tue, 10 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/profitable-parenting</guid></item><item><title>My LLM Workflow: ChatGPT, Copilot, and Other Tools</title><link>https://prashamhtrivedi.in/post/my_llm_usage/</link><description>Generative AI, LLMs and OpenAI have swept accross many aspects of our lifestyle since they are released to General Public. People are taking it across the spectrum. Some of them love it, some of them don&amp;rsquo;t like it and some of them are waiting for the dust to settle. I am one of those who love it and using it in many aspect of my life. In this post, I will tell you how I am using it and what are some tools under my belt.</description><author>Prasham H Trivedi</author><pubDate>Tue, 10 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://prashamhtrivedi.in/post/my_llm_usage/</guid></item><item><title>Reading S3 data from local PySpark</title><link>https://alexmolas.com/2023/10/10/local-pyspark-s3.html</link><description>blabla</description><author>Alex Molas Blog</author><pubDate>Tue, 10 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://alexmolas.com/2023/10/10/local-pyspark-s3.html</guid></item><item><title>Build the Framework You Need</title><link>https://www.gregnavis.com/articles/build-the-framework-you-need.html</link><description>Or how to retain the vanilla Rails feel beyond the early stages.</description><author>Greg Navis</author><pubDate>Tue, 10 Oct 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://www.gregnavis.com/articles/build-the-framework-you-need.html</guid></item><item><title>【Compose】平滑的解决圆角矩形长度过短的显示问题</title><link>https://0xzhangke.github.io/computer/2023/10/09/SlickRoundedCornerShape.html</link><description>&lt;p&gt;最近在用到圆角矩形时发现一个问题，如果控件宽度太小，小于圆角直径的话，Compose 会做一些特殊的处理，即按照比例缩小圆角半径，让其仍然看起来是个圆角矩形，如下图。&lt;/p&gt;

&lt;p&gt;&lt;img alt="Untitled" src="/assets/img/post/slick-rounded-corner-shape/Untitled.png" /&gt;&lt;/p&gt;

&lt;p&gt;对于有些场景，例如上面的场景，这样做就不太合理了，我们希望圆角半径仍然保持不变，就是这样。&lt;/p&gt;

&lt;p&gt;&lt;img alt="Untitled" src="/assets/img/post/slick-rounded-corner-shape/Untitled1.png" /&gt;&lt;/p&gt;

&lt;p&gt;本篇文章内容就是关于如何实现上面的效果。&lt;/p&gt;

&lt;h1 id="shape"&gt;Shape&lt;/h1&gt;

&lt;p&gt;本来我是想直接通过 &lt;code class="language-plaintext highlighter-rouge"&gt;Canvas&lt;/code&gt; 来绘制，但发现这么做不够通用，Compose 中一般通过 &lt;code class="language-plaintext highlighter-rouge"&gt;*Shape*&lt;/code&gt; 来描述形状，而上面的情况也属于一种圆角矩形形状，那么如果我可以通过定义一个能解决上述情况的 &lt;em&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;Shape&lt;/code&gt;&lt;/em&gt; 的话就可以应用在很多地方了。&lt;/p&gt;

&lt;h1 id="slickroundcornershape"&gt;SlickRoundCornerShape&lt;/h1&gt;

&lt;p&gt;Compose 中提供了一个 &lt;code class="language-plaintext highlighter-rouge"&gt;Shape&lt;/code&gt; 接口，其中包含一个方法，我们自定义 &lt;code class="language-plaintext highlighter-rouge"&gt;Shape&lt;/code&gt; 时主要就是实现这个方法。&lt;/p&gt;

&lt;div class="language-kotlin highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Immutable&lt;/span&gt;
&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Shape&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/**
     * Creates [Outline] of this shape for the given [size].
     *
     * @param size the size of the shape boundary.
     * @param layoutDirection the current layout direction.
     * @param density the current density of the screen.
     *
     * @return [Outline] of this shape for the given [size].
     */&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;createOutline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;layoutDirection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;LayoutDirection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;density&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Density&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Outline&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;入参就是当前控件的信息，&lt;code class="language-plaintext highlighter-rouge"&gt;size&lt;/code&gt; 是控件的大小，返回值是个 &lt;code class="language-plaintext highlighter-rouge"&gt;Outline&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;在创建 &lt;code class="language-plaintext highlighter-rouge"&gt;Outline&lt;/code&gt; 时我们可以参考已有的 &lt;code class="language-plaintext highlighter-rouge"&gt;RoundedCornerShape&lt;/code&gt;.&lt;/p&gt;

&lt;div class="language-kotlin highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;createOutline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;topStart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;topEnd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;bottomEnd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;bottomStart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;layoutDirection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;LayoutDirection&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topStart&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;topEnd&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;bottomEnd&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;bottomStart&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="mf"&gt;0.0f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Outline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Rectangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toRect&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Outline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Rounded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;RoundRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;rect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toRect&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;topLeft&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CornerRadius&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;layoutDirection&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;Ltr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;topStart&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;topEnd&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;topRight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CornerRadius&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;layoutDirection&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;Ltr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;topEnd&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;topStart&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;bottomRight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CornerRadius&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;layoutDirection&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;Ltr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;bottomEnd&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;bottomStart&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;bottomLeft&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CornerRadius&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;layoutDirection&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;Ltr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;bottomStart&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;bottomEnd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我们要处理的是 &lt;code class="language-plaintext highlighter-rouge"&gt;RoundedCornerShape&lt;/code&gt; 的特殊情况，即宽度小于圆角直径的情况，所以在这基础上我们需要添加一个判断分支来处理。&lt;/p&gt;

&lt;div class="language-kotlin highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topStart&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;topEnd&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;bottomEnd&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;bottomStart&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="mf"&gt;0.0f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Outline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Rectangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toRect&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topStart&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;bottomStart&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topStart&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;2F&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Outline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Rounded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;RoundRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;rect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toRect&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;topLeft&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CornerRadius&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;layoutDirection&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;Ltr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;topStart&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;topEnd&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;topRight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CornerRadius&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;layoutDirection&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;Ltr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;topEnd&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;topStart&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;bottomRight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CornerRadius&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;layoutDirection&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;Ltr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;bottomEnd&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;bottomStart&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;bottomLeft&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CornerRadius&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;layoutDirection&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;Ltr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;bottomStart&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;bottomEnd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里为了简单起见，我们只处理 &lt;code class="language-plaintext highlighter-rouge"&gt;topStart == bottomStart&lt;/code&gt; 的情形。&lt;/p&gt;

&lt;p&gt;现在新增的 &lt;code class="language-plaintext highlighter-rouge"&gt;if&lt;/code&gt; 分支就是我们要处理的场景了。&lt;/p&gt;

&lt;p&gt;这里还包含了两种情况：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;控件高度小于等于圆角直径，此时左侧只有一个半圆。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img alt="Untitled" src="/assets/img/post/slick-rounded-corner-shape/Untitled2.png" /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;控件高度大于圆角直径，此时左侧包含上下两个圆角和一条连接直线。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img alt="Untitled" src="/assets/img/post/slick-rounded-corner-shape/Untitled3.png" /&gt;&lt;/p&gt;

&lt;p&gt;针对上述两种情况，我们可以通过构建不同的 Path 来实现。&lt;/p&gt;

&lt;div class="language-kotlin highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;radius&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;topStart&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;radius&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;buildSlickRoundCornerPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;buildSingleArcPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;Outline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Generic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;第一种情况是比较容易处理的。&lt;/p&gt;

&lt;div class="language-kotlin highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;buildSingleArcPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;moveTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0F&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arcTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;rect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Rect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;radius&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;2F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;radius&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;2F&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;startAngleDegrees&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;90F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;sweepAngleDegrees&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;180F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;forceMoveTo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;逻辑比较简单，先移动到空间的右上角，然后绘制一个圆弧，这个圆弧的半径就是圆角半径，然后再闭合这个 &lt;code class="language-plaintext highlighter-rouge"&gt;Path&lt;/code&gt; 就行了。&lt;/p&gt;

&lt;p&gt;我们主要来看第二种情况。&lt;/p&gt;

&lt;p&gt;比较麻烦的是需要根据空间的宽度动态调整控件绘制区域的高度。&lt;/p&gt;

&lt;p&gt;&lt;img alt="Untitled" src="/assets/img/post/slick-rounded-corner-shape/Untitled4.png" /&gt;&lt;/p&gt;

&lt;p&gt;图中黄色线断表示控件的宽度，此时我们需要计算红线的长度，然后绘制这部分的圆弧。&lt;/p&gt;

&lt;p&gt;还好我们有老祖宗的智慧：&lt;strong&gt;勾股定理&lt;/strong&gt;。&lt;/p&gt;

&lt;div class="language-kotlin highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;arcHeight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;radius&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;radius&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;radius&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;radius&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;那么圆弧到控件顶部和底部的距离就是：&lt;/p&gt;

&lt;div class="language-kotlin highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;yOffset&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;radius&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;arcHeight&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;因此上半部分的圆弧可以这么绘制出来：&lt;/p&gt;

&lt;div class="language-kotlin highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arcTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;rect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Rect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yOffset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;2F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;bottom&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yOffset&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;arcHeight&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;2F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;startAngleDegrees&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;180F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sweepAngleDegrees&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;90F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;forceMoveTo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后我们把 &lt;code class="language-plaintext highlighter-rouge"&gt;Path&lt;/code&gt; 移动到下面圆弧的最底端：&lt;/p&gt;

&lt;div class="language-kotlin highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;bottomArcBottom&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;yOffset&lt;/span&gt;
&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lineTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bottomArcBottom&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arcTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;rect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Rect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bottomArcBottom&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;arcHeight&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;2F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;bottom&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bottomArcBottom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;startAngleDegrees&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;90F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sweepAngleDegrees&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;90F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;forceMoveTo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;上下两个圆弧的大小一致，参数也都差不多，就不用详细介绍了。&lt;/p&gt;

&lt;p&gt;然后再封闭 &lt;code class="language-plaintext highlighter-rouge"&gt;Path&lt;/code&gt; 即可。&lt;/p&gt;

&lt;div class="language-kotlin highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lineTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;yOffset&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;arcHeight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样差不多就能实现上面的效果了。&lt;/p&gt;

&lt;p&gt;使用起来也跟普通的 &lt;code class="language-plaintext highlighter-rouge"&gt;Shape&lt;/code&gt; 一样。&lt;/p&gt;

&lt;div class="language-kotlin highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="n"&gt;modifier&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clipToBounds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;background&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Blue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alpha&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.3F&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;shape&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SlickRoundedCornerShape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// or&lt;/span&gt;
&lt;span class="n"&gt;modifier&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SlickRoundedCornerShape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;background&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Blue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alpha&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.3F&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;好了，这篇文章就这么多了，实现总体上比较简单，但有些小细节需要注意，所以直接把它分享出来，大家遇到这个问题的话可以直接拿去用。&lt;/p&gt;

&lt;p&gt;Compose 发展比较晚，生态还不是特别完善，在应对复杂需求时可能会遇到很多诸如此类的小问题，原本用 View 可以快速实现的东西到了 Compose 这里就需要花不少时间，所以 Compose 社区生态还是要靠我们开发者逐渐完善。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/0xZhangKe/compose-ext/blob/main/SlickRoundedCornerShape/src/main/java/com/zhangke/compose/ext/ui/SlickRoundedCornerShape.kt"&gt;点击这里&lt;/a&gt;查看完整代码。&lt;/p&gt;</description><author>张可的博客</author><pubDate>Mon, 09 Oct 2023 16:47:40 GMT</pubDate><guid isPermaLink="true">https://0xzhangke.github.io/computer/2023/10/09/SlickRoundedCornerShape.html</guid></item><item><title>Zimaboard: the closest thing to my dream home server setup</title><link>https://ounapuu.ee/posts/2023/10/09/zimaboard/</link><description>I gave in to my impulses and bought myself a small single board computer to be my power-efficient home server, here's how it went.</description><author>./techtipsy</author><pubDate>Mon, 09 Oct 2023 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ounapuu.ee/posts/2023/10/09/zimaboard/</guid></item><item><title>Please to meet you, hope you guessed my name?</title><link>https://blog.steve.fi/please_to_meet_you__hope_you_guessed_my_name_.html</link><description>&lt;p&gt;"Hello, my name is Steve" - those are words I've said a million times in my life, however they are not true words.&lt;/p&gt;

&lt;p&gt;If you want to get all &lt;em&gt;technical&lt;/em&gt; about things, my name has always been SteveN.&lt;/p&gt;

&lt;p&gt;Mostly this hasn't mattered to me, or anybody else, I introduce myself as Steve, people call me Steve, and Steve is the name that makes me turn my head, when shouted across a bar.    However things changed when I moved to Finland.&lt;/p&gt;

&lt;p&gt;In Finland I had to open new bank accounts, sign mortgages, hand over IDs, and there were many many pieces of paper I signed, or forms I filled out.  Unfortunately I screwed up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If I were thinking clearly I'd think "Oh, this is something &lt;em&gt;official&lt;/em&gt;, I'd best write SteveN".&lt;/li&gt;
&lt;li&gt;If I were distracted, or not being careful I'd write my name as "Steve", and then sign it as Steve.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The end result?  I've been in Finland for approximately eight years, and I have some official documentation calling me Steve, and some other official documentation calling myself Steven.  (For example my "Permanent Residency Permit" calls me Steve, but my Social Security ID knows me as Steven.)&lt;/p&gt;

&lt;p&gt;Every now and again somebody queries the mismatch, and there are daily moments of pain where I have to interact with different agencies, so I made the obvious decision:  I'm gonna change my name.&lt;/p&gt;

&lt;p&gt;A fee of €60 and a simple online form was sufficient to initiate the process.  The processing time was given as "one to five months" on the &lt;a href="https://dvv.fi/en/changing-forename" rel="nofollow"&gt;official forename changing page&lt;/a&gt;, but happily the process was complete in a month.&lt;/p&gt;

&lt;p&gt;I will now need to do a little housekeeping by getting updated bank-cards, etc, and then complete the process by changing my UK passport to match.  Hopefully this won't take too long - but I guess if Finland knows me as Steve and the UK knows me as Steven I'll still be in a bit of a screwed up state, albeit one that is consistent in each country!&lt;/p&gt;

&lt;p&gt;Not a big change really, but also it feels weird to suddenly say "Hello, my name is Steve" and mean it.&lt;/p&gt;

&lt;p&gt;People are weird.&lt;/p&gt;

&lt;p&gt;Names are interesting.&lt;/p&gt;

&lt;p&gt;The end.&lt;/p&gt;

&lt;p&gt;Fin.&lt;/p&gt;</description><author>Steve Kemp's Blog</author><pubDate>Mon, 09 Oct 2023 12:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.steve.fi/please_to_meet_you__hope_you_guessed_my_name_.html</guid></item><item><title>A Weekend with Code Llama</title><link>https://zdwolfe.medium.com/a-weekend-with-code-llama-f6a964f4b92f?source=rss-65d40a7904f------2</link><description>&lt;p&gt;I used the Code Llama LLM with IDE integration for a weekend, it’s pretty neat. If you’re a software engineer curious about code-generation LLMs, you should try it!&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yzEKm0YGuYhaXlDz-z1n0g.jpeg" /&gt;&lt;figcaption&gt;Photo by &lt;a href="https://unsplash.com/@paulequay?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash"&gt;Paul Lequay&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/73FOXT1DvjI?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash"&gt;Unsplash&lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;I’ve been using ChatGPT in personal coding side projects for several months. I find it useful to solve simple tasks like parsing strings, making HTTP calls, etc, and discovering interface or language features. This allows me to spend more energy on what I actually find enjoyable in weekend side-projects.&lt;/p&gt;&lt;p&gt;My workflow is generally to get a coding task to “work” by myself with some mocked-out classes or methods. Once happy, I give an English description of the mocks’ expected behavior, and some examples to ChatGPT. I then fold the resulting generated code into my project, modifying it myself to get that last 25% fit/finish.&lt;/p&gt;&lt;p&gt;After reading about Meta’s open-source Code Llama model &lt;a href="https://ai.meta.com/blog/code-llama-large-language-model-coding/"&gt;in August 2023&lt;/a&gt;, I realized a fully-local, project-aware LLM coding tool was on the horizon.&lt;/p&gt;&lt;p&gt;Last week I discovered Ollama, a fantastic wrapper around Llama-based models now has an &lt;a href="https://ollama.ai/blog/ollama-is-now-available-as-an-official-docker-image"&gt;official docker image&lt;/a&gt; to abstract away all the typical AI environment setup horrors. With that development, I decided it was finally time to give LLMs in my IDE a try!&lt;/p&gt;&lt;p&gt;The tools I used are:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;a href="https://ollama.ai/"&gt;Ollama&lt;/a&gt; — the fore-mentioned Llama wrapper that runs locally&lt;/li&gt;&lt;li&gt;&lt;a href="https://ai.meta.com/resources/models-and-libraries/llama/"&gt;Llama 2&lt;/a&gt; codellama:13b model&lt;/li&gt;&lt;li&gt;&lt;a href="https://continue.dev/"&gt;Continue&lt;/a&gt; — a VSCode plugin to enable LLM-assisted coding.&lt;/li&gt;&lt;li&gt;&lt;a href="https://code.visualstudio.com/"&gt;VSCode&lt;/a&gt; — my preferred IDE for Typescript&lt;/li&gt;&lt;li&gt;&lt;a href="https://www.docker.com/"&gt;Docker &lt;/a&gt;and &lt;a href="https://learn.microsoft.com/en-us/windows/wsl/install"&gt;Windows Subsystem for Linux&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h3&gt;My Experience&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; My experience coding for a weekend (~6–8 hours) with Code Llama 13b in my IDE was a mild convenience and latency improvement over a browser-based chatbot like ChatGPT, with a minor quality regression, especially while I was learning how it “likes” to be prompted.&lt;/p&gt;&lt;p&gt;Regardless, the privacy, latency, and in-IDE user experience improvements over ChatGPT coding help were enough for me to continue to use it moving forward.&lt;/p&gt;&lt;h3&gt;Example: Refactoring and Logging&lt;/h3&gt;&lt;p&gt;Below is a representative example of my first-try experience.&lt;/p&gt;&lt;p&gt;One of the things I did over the weekend was add minor logging improvements to a gaming side project to troubleshoot a bug. While I was adding log statements, I noticed something that was worth refactoring, and had the thought to try this Code Llama tool setup.&lt;/p&gt;&lt;p&gt;I had a simple debug method which accepted an object: any. debug conditionally writes the log to avoid serialization in tight loops and huge inputs, which meaningfully slows down my API's latency.&lt;/p&gt;&lt;p&gt;A pattern I developed when using debug in my application was to emit the calling method as a part of the object for traceability:&lt;/p&gt;&lt;pre&gt;debug({ method: 'getReplaySummary', options });&lt;/pre&gt;&lt;p&gt;I imagined there was some reflection- or stack-inspection way to get the calling method from within debug, making the method a bit cleaner to use, and resilient to renaming:&lt;/p&gt;&lt;pre&gt;debug({ options });&lt;/pre&gt;&lt;p&gt;I’m a novice at JS/TS and didn’t know off the top of my head how to get that information.&lt;/p&gt;&lt;p&gt;So I asked Code Llama!&lt;/p&gt;&lt;p&gt;I highlighted the relevant code, then used the prompt:&lt;/p&gt;&lt;blockquote&gt;&lt;em&gt;The ‘debug’ method takes an object with a ‘method’ key. As you see, the method is ‘getReplaySummary’. How would I change the internal implementation of ‘debug’ to calculate the calling method’s name automatically.&lt;/em&gt;&lt;/blockquote&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*G4kX8alf2Zrm-_tCI8yLgQ.png" /&gt;&lt;/figure&gt;&lt;p&gt;Code Llama’s response (on the left of the above image) was useful, and the in-IDE LLM UX was convenient. But this isn’t a mind-blowing improvement over ChatGPT or even a Google search, the only real difference is using the provided highlighted context to name the methods. This “conversation” is essentially the #1 hit for the &lt;a href="https://stackoverflow.com/questions/280389/how-do-you-find-out-the-caller-function-in-javascript"&gt;equivalent Google search&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;When I added context (the entire contents of logger.ts) to the conversation, it didn't quite work as expected, but in my mind shows where this tech is going. What I wanted was to change the method, but Code Llama didn’t quite read my mind enough, instead adding a somewhat strange wrapper class.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/794/1*y9a0AD52AqUB1UtS8nlO9g.png" /&gt;&lt;/figure&gt;&lt;p&gt;Once I was direct about what I wanted, &lt;em&gt;just change the existing debug method&lt;/em&gt;, it made the suggestion, after a fashion. It didn’t understand the spirit of the refactoring, but again I can see where this tech is going.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/797/1*9PBrznH1wfhV93Iwyr1TyA.png" /&gt;&lt;/figure&gt;&lt;p&gt;What I wanted was: this use of debug:&lt;/p&gt;&lt;pre&gt;export async function getReplaySummary(options: ReplayOptions) ... {&lt;br /&gt;  debug({ options });&lt;br /&gt;  ...&lt;/pre&gt;&lt;p&gt;to result in this log message:&lt;/p&gt;&lt;pre&gt;{&lt;br /&gt;  &amp;quot;method&amp;quot;: &amp;quot;getReplaySummary&amp;quot;,&lt;br /&gt;  &amp;quot;options&amp;quot; { ... } &lt;br /&gt;}&lt;/pre&gt;&lt;p&gt;From the above response, I could easily take the solution generated by Code Llama and fold it into my project. But I was curious, and tried to get Code Llama to write exactly what I wanted.&lt;/p&gt;&lt;p&gt;What transpired was a pretty frustrating oscillation of context loss and intent. Summarized, the exchange was:&lt;/p&gt;&lt;blockquote&gt;&lt;em&gt;Me: I want this logged: { options: options, method: ‘getReplaySummary’ }&lt;br /&gt;Llama:&lt;/em&gt;&lt;/blockquote&gt;&lt;pre&gt;export const debug = (object: any) =&amp;gt; {&lt;br /&gt;  if (l.isLevelEnabled('debug')) {&lt;br /&gt;    l.debug(`${JSON.stringify(object)}`, { method: 'getReplaySummary' });&lt;br /&gt;  }&lt;br /&gt;};&lt;/pre&gt;&lt;blockquote&gt;&lt;em&gt;Me: This does not log one object, it logs with two parameters, and doesn’t calculate the method automatically.&lt;br /&gt;Llama: You’re correct! I apologize for the confusion. [Me: lol.. ok… so?]&lt;br /&gt;Me: Make it log one object.&lt;br /&gt;Llama:&lt;/em&gt;&lt;/blockquote&gt;&lt;pre&gt;export const debug = (object: any) =&amp;gt; {&lt;br /&gt;  if (l.isLevelEnabled('debug')) {&lt;br /&gt;    l.debug(`${JSON.stringify(object)}`, { method: 'getReplaySummary' });&lt;br /&gt;  }&lt;br /&gt;};&lt;/pre&gt;&lt;blockquote&gt;&lt;em&gt;[Llama forgot the previous parts of the prompt to log the method]&lt;br /&gt;Me: Remember that I want to calculate the ‘method’ key automatically.&lt;/em&gt;&lt;/blockquote&gt;&lt;pre&gt;export const debug = (object: any) =&amp;gt; {&lt;br /&gt;  if (l.isLevelEnabled('debug')) {&lt;br /&gt;    l.debug(`${JSON.stringify(object)}`, { method: arguments.callee });&lt;br /&gt;  }&lt;br /&gt;};&lt;/pre&gt;&lt;blockquote&gt;&lt;em&gt;[Llama forgot the previous parts of the prompt to log as one object]&lt;/em&gt;&lt;/blockquote&gt;&lt;p&gt;…and so it continued oscillating until I gave up.&lt;/p&gt;&lt;p&gt;What it looks like is 13b wasn't able to utilize the multiple instructions and clarifications in the context fully — it “forgot” about the previous conversation.&lt;/p&gt;&lt;p&gt;After a few more interactions like this, I realized that getting the prompt right (&lt;a href="https://medium.com/aimonks/code-llama-quick-start-guide-and-prompt-engineering-eb1de8758399"&gt;prompt engineering&lt;/a&gt;) dramatically changes the response. For example:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Examples of input/output/side-effects help a lot&lt;/li&gt;&lt;li&gt;Including what you &lt;em&gt;do not&lt;/em&gt; want to happen helps too&lt;/li&gt;&lt;li&gt;Don’t use jargon or keywords that aren’t included in the context&lt;/li&gt;&lt;li&gt;Avoiding use of libraries, unless you also include them in the context&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;The prompt that eventually got the debug function to what I wanted followed the tips 1, 3, 4.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/797/1*JePxpI_xUlxtJIsTaF2lRA.png" /&gt;&lt;/figure&gt;&lt;p&gt;In the end, it wasn’t immediately worth the time, in this example, to use Code Llama to completely solve this simple task. The real value-add was the quick, context-aware nudge towards the language feature arguments.callee I didn't know about. I can also see that after I get some prompt engineering skill it could be used to fully solve problems the same way I use ChatGPT.&lt;/p&gt;&lt;h3&gt;Setup and Configuration&lt;/h3&gt;&lt;p&gt;This post isn’t meant to be a setup guide for the above-mentioned tools, mostly I wanted to share my experience. For detailed instructions, visit the above-mentioned websites. In total, having WSL and Docker previously installed and no previous experience with either Ollama or VSCode plugins, setup took me about one hour.&lt;/p&gt;&lt;p&gt;Below are a few one-liners and configs that might help:&lt;/p&gt;&lt;h4&gt;Ollama&lt;/h4&gt;&lt;p&gt;As I wanted local-only LLM tools, I ran ollama within a docker network that disabled outgoing connections (&lt;a href="https://stackoverflow.com/questions/69544970/how-to-prevent-internet-access-in-docker-containers"&gt;ref&lt;/a&gt;). There’s probably a more secure way to do this, but having a no-internet docker network is a handy tool for situations like this.&lt;/p&gt;&lt;pre&gt;docker network create \&lt;br /&gt;  --driver bridge \&lt;br /&gt;  --subnet 172.18.0.0/16 \&lt;br /&gt;  --gateway 172.18.0.1 \&lt;br /&gt;  no-internet&lt;/pre&gt;&lt;p&gt;..then boot up the ollama container and watch the logs.&lt;/p&gt;&lt;pre&gt;docker run \&lt;br /&gt;  --network no-internet \&lt;br /&gt;  --detach \&lt;br /&gt;  --gpus=all \&lt;br /&gt;  --volume ollama:/root/.ollama \&lt;br /&gt;  --port 11434:11434 \&lt;br /&gt;  --name ollama \&lt;br /&gt;  ollama/ollama &lt;br /&gt;docker logs --follow /ollama&lt;/pre&gt;&lt;p&gt;And finally, start prompting it via the Continue tab in VSCode’s left-side plugin bar.&lt;/p&gt;&lt;h4&gt;Continue&lt;/h4&gt;&lt;p&gt;After installing Continue, the VSCode plugin that connects to Code Llama (among other models), I selected the codellama:13b model (see why below), using the model selector.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/401/1*MKCWsYM_xhzgB8GNflfnfw.png" /&gt;&lt;/figure&gt;&lt;p&gt;I adjusted the temperature and &lt;a href="https://continue.dev/docs/telemetry"&gt;disabled telemetry&lt;/a&gt; in the UI-referenced config.py.&lt;/p&gt;&lt;pre&gt;config = ContinueConfig(&lt;br /&gt;    allow_anonymous_telemetry=False,&lt;br /&gt;    models=Models(&lt;br /&gt;		default=Ollama(&lt;br /&gt;			title=&amp;quot;codellama:13b&amp;quot;,&lt;br /&gt;			system_message=&amp;quot;&amp;quot;,&lt;br /&gt;			model=&amp;quot;codellama:13b&amp;quot;&lt;br /&gt;		)&lt;br /&gt;	),&lt;br /&gt;    system_message=&amp;quot;&amp;quot;,&lt;br /&gt;    temperature=0.85,&lt;/pre&gt;&lt;h3&gt;Crashing&lt;/h3&gt;&lt;p&gt;One issue I did have through my weekend trial, was crashes. The ollama process crashes frequently, mostly when it runs out of memory. Additionally, when this would happen, Continue would also hang.&lt;/p&gt;&lt;p&gt;When Code Llama is active, responding to a query or generating code, it use an enormous amount of memory.&lt;/p&gt;&lt;p&gt;After anywhere from 3–20 interactions (query, response cycles) with the 34b parameter model, the context seems to build up to the point where even on my pretty beefy system, with 32GB RAM and RTX 4090/24GB VRAM it segfaulted from running out of memory.&lt;/p&gt;&lt;p&gt;Here you can see the conversation happening in the VRAM graph:&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/885/1*lUMvaA0WPKrNUCbj3D0pNg.png" /&gt;&lt;/figure&gt;&lt;p&gt;My first stab to solve this this was to relax to the 13b parameter model, which stopped crashing enough for me to continue experimenting.&lt;/p&gt;&lt;p&gt;I wasn’t able to immediately tell the difference in responses at a glance, but from what &lt;a href="https://news.ycombinator.com/item?id=37267597"&gt;I’ve read&lt;/a&gt;, the higher parameter models do perform much better. I think this is probably something I can find a workaround for, and I intend to, but I haven’t yet. If you have any ideas, let me know!&lt;/p&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;&lt;em&gt;Was my first experience using Code Llama for weekend coding mind-blowing?&lt;/em&gt;&lt;br /&gt;No, not really..&lt;br /&gt;..but it was cool, and I’m excited about LLM-assisted IDEs in a year or two.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Was it useful?&lt;/em&gt;&lt;br /&gt;About as useful as Google and Stack Overflow, which is to say, very!&lt;/p&gt;&lt;p&gt;&lt;em&gt;Will I keep experimenting with Code Llama?&lt;/em&gt;&lt;br /&gt;Absolutely!&lt;/p&gt;&lt;img alt="" height="1" src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=f6a964f4b92f" width="1" /&gt;</description><author>Stories by Zach Wolfe on Medium</author><pubDate>Mon, 09 Oct 2023 05:13:42 GMT</pubDate><guid isPermaLink="true">https://zdwolfe.medium.com/a-weekend-with-code-llama-f6a964f4b92f?source=rss-65d40a7904f------2</guid></item><item><title>Spreadsheet to visualisation: How I created towing.utf9k.net</title><link>https://utf9k.net/blog/spreadsheet-to-visualisation/</link><description>How I finished a project that was sitting on the backburner for years</description><author>utf9k</author><pubDate>Mon, 09 Oct 2023 05:00:00 GMT</pubDate><guid isPermaLink="true">https://utf9k.net/blog/spreadsheet-to-visualisation/</guid></item><item><title>The top bugs all AI developer tools are suffering from</title><link>https://zackproser.com/blog/top-ai-dev-tools-bugs</link><description>I have evaluated everything from ChatGPT to CoPilot to Codeium to Cursor, to Sourcegraph Cody to CodiumAI. All suffer from the same bugs</description><author>Zachary Proser</author><pubDate>Mon, 09 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/top-ai-dev-tools-bugs</guid></item><item><title>A student asked how I keep us innovative. I don't.</title><link>https://ntietz.com/blog/forefront-of-innovation/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;Last week, I did a Q&amp;amp;A session for a friend's security class.
One of the students asked a question that I loved.
They asked something like, "As a principal engineer, how do you make sure your company stays at the forefront of innovation?"&lt;/p&gt;
&lt;p&gt;There are two reasons I love this question.
The first is that it's a good and natural one, which I had early on too.
The second is that it's &lt;em&gt;unintentionally leading&lt;/em&gt;.
It assumes you &lt;em&gt;should&lt;/em&gt; be working at the leading edge of innovative technology.&lt;/p&gt;
&lt;p&gt;And that's why my answer started with "I don't. I make sure we &lt;em&gt;don't&lt;/em&gt;."
A leading question gets a snappy answer!
But that's not the whole story, of course.&lt;/p&gt;
&lt;p&gt;The key is to understand &lt;em&gt;why&lt;/em&gt; you don't want to be on the leading edge of innovation all the time, and also to understand &lt;em&gt;when&lt;/em&gt; it's appropriate.&lt;/p&gt;
&lt;h1 id="why-we-use-proven-technology"&gt;Why we use proven technology&lt;/h1&gt;
&lt;p&gt;Most of the time, the problems you run into while doing your work are mundane.
The vast majority of your hot new startup is things that have been done before.
For any new web app you're going to have users, logins, a frontend, a database.&lt;/p&gt;
&lt;p&gt;For each of these, you could use something hot and new.
You could tie your users to some public blockchain (sorry).
You could come up with a novel new way of logging in (please, please, no).
The frontend can be built with that new framework you saw on HackerNews last week (or is that already out of date?).
And of course, the database should be a NoSQL, graph, or vector database depending on which hype wave you caught.&lt;/p&gt;
&lt;p&gt;Each of these bring advantages, no doubt.
There's a reason I spent years working on graph databases: they're dope technology that can solve some real problems.
There's also a reason I've talked many people out of using them.&lt;/p&gt;
&lt;p&gt;When you adopt a new innovative technology, you're giving up a lot.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Proven technologies are searchable and have robust documentation.&lt;/strong&gt;
Have a problem with PostgreSQL? Pop it into a search engine and you'll get an answer right away.
But have a problem with a vector DB? Comb the GitHub Issues or Discord and hope that you find an answer 🙏.
This can save you so much time when you inevitably run into problems.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;They often have great ecosystems around them.&lt;/strong&gt;
With proven tech, like PostgreSQL, you will usually have great packages and integrations.
Your well-known DB and well-known observability provider probably get cozy and integrate well.
Your favorite language has drivers for this time-tested DB.
But with the new stuff? You're writing a lot of that yourself, or patching it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;They use well-known concepts.&lt;/strong&gt;
Proven technologies have kind of by definition been around a while.
This means you can (more) reasonably expect people to know the core concepts.
Most software developers are probably familiar with relational DBs, but far fewer are familiar with graph DBs.
Well-known concepts are accelerants: they let you converse more quickly, design more quickly, understand more quickly.
New concepts are a tax which slow you down as you have to understand it and fit everything into that new model.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There's a lot to love with the proven stuff.
This isn't a new or novel opinion: there are a &lt;em&gt;lot&lt;/em&gt; of advocates of &lt;a href="https://boringtechnology.club/"&gt;choosing boring technology&lt;/a&gt;.
It's a strategy that I expect technical leaders to employ, and it's a red flag if teams are eschewing tons of the boring stuff.
It means they probably don't have a good technical strategy and strong leadership.&lt;/p&gt;
&lt;p&gt;That said, sometimes it &lt;em&gt;is&lt;/em&gt; justified.&lt;/p&gt;
&lt;h1 id="when-and-how-to-use-innovative-new-tech"&gt;When (and how) to use innovative new tech&lt;/h1&gt;
&lt;p&gt;The reason we build software is to get something done, to solve some problem.
That destination is what guides our adoption of technologies.&lt;/p&gt;
&lt;p&gt;With any given choice, the question is: does this technology &lt;em&gt;fundamentally alter&lt;/em&gt; my chances of solving this problem?
If the answer is "no", then just go with the boring choice.
It doesn't make a difference, so why would you give up the benefits?
If the answer is "yes, it makes us much more likely to succeed," then you get to move on.&lt;/p&gt;
&lt;p&gt;Now you have to figure out why.
Committing to this should be done eyes wide open, so figure out the specific reasons that this technology is necessary.
Contrast using it with using the boring choice, and try to figure out the properties that it gives you that you need.
Once you've found that irreducible property that greatly aids in solving the problem, and it cannot be done with boring one?
&lt;em&gt;That&lt;/em&gt; is when you reach for the shiny new thing, and you go in eyes wide open.
With the bleeding edge, &lt;em&gt;you are going to get cut&lt;/em&gt;, but sometimes that's necessary.&lt;/p&gt;
&lt;p&gt;Use the boring things until you absolutely cannot succeed with it, and you'll get a lot further a lot faster.&lt;/p&gt;
&lt;h1 id="my-framework-for-choosing-technologies"&gt;My framework for choosing technologies&lt;/h1&gt;
&lt;p&gt;Part of technical leadership is being involved in technical design and choosing what to use.
Here's my general approach for doing that (at work&lt;sup class="footnote-reference"&gt;
  &lt;a href="https://ntietz.com/blog/forefront-of-innovation/#fn-1" id="fn-ref-1"&gt;1&lt;/a&gt;
&lt;/sup&gt;
).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;First, understand the problem.&lt;/strong&gt;
This is similar to how we &lt;a href="https://ntietz.com/blog/how-i-debug-2023/"&gt;approach debugging&lt;/a&gt;, because both are a form of problem solving.
If you don't have a clear understanding of the problem at hand, then you cannot solve it, and you cannot pick the right tech to use.
I like to test my understanding by explaining the problem to a lay person.
If I can explain it in a relatively clear way, then I understand the problem well enough to proceed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Then, prove that a solution exists&lt;/strong&gt;.
This "existence proof" of a solution is &lt;em&gt;always&lt;/em&gt; my first step, because if you cannot get &lt;em&gt;anything&lt;/em&gt; working it doesn't matter, the problem isn't getting solved.
It also allows you a lot of creative freedom.
The outcome is a design document showing some valid solution to the problem.
In this step I'll allow myself to use whatever technology comes to mind.
Can I solve this with that shiny DB and my favorite programming language?
The only point is to prove that &lt;em&gt;a&lt;/em&gt; solution can exist.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Now reduce down the solution.&lt;/strong&gt;
Now that you have a proof of a solution, you can reduce it down to its essential complexity.
For each component in your design, what role does it serve, why did you include it?
Go deep and determine the absolute properties that each piece provides, and question if you need those properties or can achieve them another way.
Then iterate on your design, cutting out unnecessary things.
Refactor pieces of the design.
Add new pieces, remove old pieces, play with it, make it sleek.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Evaluate your design again.&lt;/strong&gt;
Now that it's reduced down, look at it again and ask a few questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Does it still solve the problem at hand?&lt;/li&gt;
&lt;li&gt;Can this possibly be done in any simpler way? Why or why not?&lt;/li&gt;
&lt;li&gt;Can we use more well-known technologies instead?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once you have those answers, you'll either repeat the process or proceed on.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Socialize the design.&lt;/strong&gt;
Hopefully you've been working as a team so far up to here, but you usually can't include &lt;em&gt;everyone&lt;/em&gt; in the early design.
Now that you've reduced it as much as possible, go find some critics and socialize the design.
Find people who you think will be contrarian, and have them poke holes in the design.
&lt;em&gt;Especially&lt;/em&gt; in any new technologies or innovative things.&lt;/p&gt;
&lt;p&gt;When you have convinced your critics and yourself, you can actually move on and... wait, did we finally get to use a new piece of tech?
Yes!
And you &lt;em&gt;know&lt;/em&gt; that it's for the right reasons.
It serves a critical role in the solution and it cannot be replaced.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;So, yes.
As a principal engineer, I view it as my role to keep us &lt;em&gt;off&lt;/em&gt; the bleeding edge as much as possible.
That way, when we really do need to innovate, we have the capacity to do so.
And when we don't need to, we can go really freaking fast.&lt;/p&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 09 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/forefront-of-innovation/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Week 2 Making Embedded Systems</title><link>https://dannas.name/2023/10/09/making-embedded-systems-week2</link><description>Week 2 of 10 of the Making Embedded Systems course.</description><author>Daniel Näslund</author><pubDate>Mon, 09 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://dannas.name/2023/10/09/making-embedded-systems-week2</guid></item><item><title>Debugging cookie problems in ASP.NET Core</title><link>https://nestenius.se/net/debugging-cookie-problems/</link><description>Having answered over 1000 questions on Stack Overflow, I’ve found that cookie-related issues are a frequent challenge for developers using ASP.NET Core</description><author>Tore Nestenius</author><pubDate>Mon, 09 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://nestenius.se/net/debugging-cookie-problems/</guid></item><item><title>4 Tips for a Better Conference Experience</title><link>https://justingarrison.com/blog/2023-10-08-4-tips-for-a-better-conference-experience/</link><description>How I get the most out of attending technical conferences.</description><author>Justin Garrison's Homepage</author><pubDate>Mon, 09 Oct 2023 01:07:38 GMT</pubDate><guid isPermaLink="true">https://justingarrison.com/blog/2023-10-08-4-tips-for-a-better-conference-experience/</guid></item><item><title>The Hidden Performance Cost of NodeJS and GraphQL</title><link>https://www.softwareatscale.dev/p/the-hidden-performance-cost-of-nodejs</link><description>NodeJS and GraphQL are popular technologies for building web applications, but in my experience, they come with certain scaling and performance tradeoffs to be aware of.</description><author>Software at Scale</author><pubDate>Sun, 08 Oct 2023 23:01:32 GMT</pubDate><guid isPermaLink="true">https://www.softwareatscale.dev/p/the-hidden-performance-cost-of-nodejs</guid></item><item><title>tcpproxy 0.4 released</title><link>https://neosmart.net/blog/tcpproxy-0-4-released/</link><description>&lt;p&gt;This blog post was a bit delayed in the pipeline, but a new release of tcproxy, our educational async (tokio) rust command line proxy project, is now available for download (precompiled binaries or install via cargo). I was actually surprised &amp;#8230; &lt;a href="https://neosmart.net/blog/tcpproxy-0-4-released/"&gt;Continue reading &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
The post &lt;a href="https://neosmart.net/blog/tcpproxy-0-4-released/"&gt;tcpproxy 0.4 released&lt;/a&gt; first appeared on &lt;a href="https://neosmart.net/blog"&gt;The NeoSmart Files&lt;/a&gt;.</description><author>The NeoSmart Files</author><pubDate>Sun, 08 Oct 2023 21:53:36 GMT</pubDate><guid isPermaLink="true">https://neosmart.net/blog/tcpproxy-0-4-released/</guid></item><item><title>Auto-off power banks</title><link>https://voussoir.net/writing/auto_off_power_banks</link><description>&lt;article&gt;
&lt;p&gt;&lt;a href="/writing"&gt;Back to writing&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a class="tag_link" href="/writing/tags/electronics"&gt;[electronics]&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Auto-off power banks&lt;/h1&gt;
&lt;p&gt;The essential parts of a USB power bank are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A battery.&lt;/li&gt;
&lt;li&gt;Charge controller to receive USB input and safely charge the battery.&lt;/li&gt;
&lt;li&gt;Protection circuit to prevent the battery from being excessively discharged.&lt;/li&gt;
&lt;li&gt;Voltage regulator to convert the battery's voltage to 5 V for USB output.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Artist's rendition:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Photograph of a breadboard containing a TP4056 charge controller, a 5 V USB regulator, and an 18650 battery cell. A cable connects this breadboard to a cell phone which is being charged." src="/writing/auto_off_power_banks/breadboard1.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;But the majority of power banks on the market go the extra mile by adding a microcontroller that detects when the output current is low, and turns off the power bank.&lt;/p&gt;
&lt;p&gt;The expected use case for these products is, first and foremost, to charge cell phones. I would love to be more broad and inclusive with that, but, no, it's mainly about cell phones. The phone will draw a high current while it is charging, and a low current when it is full, so the power bank detects that and shuts off.&lt;/p&gt;
&lt;p&gt;This drives me crazy! These auto-off power banks make it impossible to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;run low-current loads like gentle ambient lights.&lt;/li&gt;
&lt;li&gt;run arduino or other microcontroller projects that draw less than the minimum current.&lt;/li&gt;
&lt;li&gt;deliberately keep the phone out of &lt;a href="https://git.voussoir.net/voussoir/trkpt#doze"&gt;doze mode&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;use an inline switch, or a device with its own power switch.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A couple of quick searches will find you plenty of electronics hobbyists and DIYers who'd rather their power banks not turn off:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://old.reddit.com/r/usbchardware"&gt;/r/usbchardware&lt;/a&gt;: &lt;a href="/writing/auto_off_power_banks/usbchardware_powerbank.html"&gt;USB powerbanks without "trickle" switching&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;arduino.cc: &lt;a href="/writing/auto_off_power_banks/arduino_powerbank.html"&gt;USB battery bank without low current shutoff&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;candlepowerforums: &lt;a href="/writing/auto_off_power_banks/candlepowerforums_powerbank.html"&gt;USB powerbank WITHOUT auto off?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;electronics.stackexchange: &lt;a href="/writing/auto_off_power_banks/stackexchange_powerbank.html"&gt;A proper way to stop power banks to turn off, and how to power low-current devices through USB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;mtbr: &lt;a href="/writing/auto_off_power_banks/mtbr_powerbank.html"&gt;Power Bank with no "on button"&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;pistonheads: &lt;a href="/writing/auto_off_power_banks/pistonheads_powerbank.html"&gt;'Always On' Powerbank without auto standby&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://old.reddit.com/r/homeautomation"&gt;/r/homeautomation&lt;/a&gt;: &lt;a href="/writing/auto_off_power_banks/homeautomation_powerbank.html"&gt;Always on powerbank?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;hackernews: &lt;a href="/writing/auto_off_power_banks/hackernews_powerbank.html"&gt;There's no reason anymore that a power bank should ever even have an off mode&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When I look at power banks for sale, the majority that I see have a single-button user interface, where pressing the button wakes up the power bank and displays the remaining charge level. Some brands, but not all, offer a low current mode that disables the auto-off feature, accessed by double-pressing the button, or holding it for a few seconds, etc. The joys of single-button UIs. This only takes effect for a single discharge session, and often comes with the caveat that it will &lt;strong&gt;still&lt;/strong&gt; turn itself off after a &lt;a href="/writing/auto_off_power_banks/xiaomi_manual.png"&gt;few hours&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For power banks that don't offer a low current mode, the most commonly recommended solution is to put a resistor across the power bank's output. At 5 V, a 50 Ohm resistor will dissipate 100 mA, for example. That's right, the solution to your low-power project is simply to draw more power! Wow! Some more ambitious problem solvers will put the resistor on a timer so it draws a burst of current just often enough to keep the battery awake and minimize waste. But this still adds a clunky, intermediate dongle to your project.&lt;/p&gt;
&lt;p&gt;Some of the very cheap power banks don't turn themselves off at all, and are valuable to the hobbyists. But they usually come from anonymous brands of no repute, which makes it impossible to provide a specific product recommendation because the brand won't exist in six months, and barely exists today. They typically don't even consider "always on" to be one of their selling points, so they don't mention it, which makes it nearly impossible to search a marketplace by keyword.&lt;/p&gt;
&lt;p&gt;What I mean is, there are plenty of auto-off power banks that don't mention they are auto-off because it's considered standard behavior; and there are always-on power banks that don't mention they are always-on because it's not considered to be a selling point. The absence of either keyword practically means you have to assume it is auto-off.&lt;/p&gt;
&lt;p&gt;One of the very, very few brands that prominently advertises their always-on power banks is &lt;a href="/writing/auto_off_power_banks/voltaic.html"&gt;Voltaic&lt;/a&gt;, and they charge a hefty price premium for what is, from this perspective, feature subtraction. The high price is accounted for by other perspectives: they're designed to charge from solar without browning out, they explicitly support charging while discharging, and they're aiming towards business clients purchasing in high volume. But that's not what I was looking for.&lt;/p&gt;
&lt;p&gt;If you want an always-on battery, the most likely candidates are the $5 unit from a no-name whitelabeler that was too cheap to include the auto-off microcontroller, or the $50+ unit from the premium B2B IOT market, it seems. There are precious few in the middle, but they do exist, sometimes. I bought &lt;a href="/writing/auto_off_power_banks/aliexpress_listing.png"&gt;this unit&lt;/a&gt; via aliexpress for $19 and now that seller has gone bust, probably because they were falsely using Xiaomi's name or committing other scams. I was desperate.&lt;/p&gt;
&lt;p&gt;So far, I haven't even talked about why power banks do the auto-off thing. I don't really know. It's probably because all the manufacturers are putting together building blocks from a small handful of suppliers and someone came up with it and now they're all the same.&lt;/p&gt;
&lt;p&gt;Ostensibly it's to save power, and that's the answer people give. Are they referring to the output voltage regulator, drawing some current even when not in use? That's a good point, I wonder what else we can do to disconnect loads when they are not in use.&lt;/p&gt;
&lt;p&gt;Artist's rendition:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Closeup photo of an on-off rocker switch" src="/writing/auto_off_power_banks/switch.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Photo of the same breadboard from before, this time with the rocker switch inserted in the circuit before the 5 V output regulator." src="/writing/auto_off_power_banks/breadboard2.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;I don't get it. Why do I see hundreds of power banks with the one-button interface, and none with a plain old on-off switch? Is that too 20th century? Even if you leave it switched on accidentally, it will still take weeks or months to self-discharge from the quiescent current. I know this because the always-on batteries I have sometimes go months without use. It's not the same as leaving a flashlight burning in your backpack.&lt;/p&gt;
&lt;p&gt;The only other way I can think auto-off saves power is that the device being charged — remember, as a product designer you should anticipate it could be anything as long as it's a cell phone — will perform more background activity while it's plugged in. As I mentioned, Android will not enter Doze mode while it detects the charger, and I'd expect iOS to also use charging as an opportunity to do more syncing or network activity or whatever. So, specifically, I'm referring to a device that has reached full capacity and the user has not unplugged it from the power bank yet, so it's using a few extra milliwatts. And that's apparently worth discarding all other low-power use cases.&lt;/p&gt;
&lt;p&gt;Some people will guess that the auto-off feature is to protect the power bank's own battery from overdischarge, but this is wrong. Low-voltage cutoff is done by the protection circuit which, ideally, is provided by a dedicated chip immediately in series with the battery. Auto-off also cannot be to protect the phone's battery, because the power bank cannot force the phone to take more power than it wants. Or else, mains-powered chargers would be popping everyone's phones.&lt;/p&gt;
&lt;p&gt;A rant about batteries, of all things. I'm sure there are bigger problems out there. But this is simply one example of an annoying and pervasive trend in tech: solved problems becoming unsolved over time due to reduction in user control. Stuff used to be made with buttons and switches so I can decide what it does. Now it is made "smart" so it can decide what it does. A slight change in the product's design can improve the experience of one group of users with no downsides for the other. I am very tired of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;devices and appliances calling themselves smart devices, where the word "smart" means it does something you didn't ask for because the designers couldn't imagine more than one use case or mode of operation. I want you to do less!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;devices that are non-configurable, non-customizable, and non-openable. I would be totally fine with a DIP switch on the inside of the unit, or soldering a jumper, if only you wouldn't use one-way plastic snaps in its construction!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;single-button user interfaces, their multi-click and hold incantations, and internal state machines taking the place of simple physical toggle switches.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;marketplaces flooded with thousands of whitelabeled copies of the same products, swamping out the few that are actually different.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;feeling dismayed by our failure to do easy things.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;
&lt;p&gt;&lt;a href="https://git.voussoir.net/voussoir/voussoir.net/commits/master/voussoir.net/writing/auto_off_power_banks/auto_off_power_banks.md"&gt;View this document's history&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://git.voussoir.net/voussoir/voussoir.net/commit/ee2ed2937b48df3a019b23481b838cacd5a5a935"&gt;&lt;time datetime="2023-10-08T10:53:11-07:00"&gt;2023-10-08&lt;/time&gt; Add auto_off_power_banks.md.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;address&gt;Contact me: writing@voussoir.net&lt;/address&gt;
&lt;p&gt;If you would like to subscribe for more, add this to your RSS reader: &lt;a href="/writing/writing.atom" rel="alternate" type="application/atom+xml"&gt;https://voussoir.net/writing/writing.atom&lt;/a&gt;&lt;/p&gt;
&lt;/article&gt;</description><author>voussoir.net/writing</author><pubDate>Sun, 08 Oct 2023 20:53:11 GMT</pubDate><guid isPermaLink="true">https://voussoir.net/writing/auto_off_power_banks</guid></item><item><title>SX-64 Tiny Reset – Count Reset Button Presses</title><link>https://www.hackup.net/2023/10/sx-64-tiny-reset-count-reset-button-presses/</link><description>&lt;p&gt;Recently I had the opportunity to buy a Commodore SX-64 for a very reasonable price. Back when I had my Breadbox C64 as a kid, I was fascinated by this portable version and dreamed of owning one myself to take &amp;#8230; &lt;a href="https://www.hackup.net/2023/10/sx-64-tiny-reset-count-reset-button-presses/"&gt;Continue reading &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
The post &lt;a href="https://www.hackup.net/2023/10/sx-64-tiny-reset-count-reset-button-presses/"&gt;SX-64 Tiny Reset – Count Reset Button Presses&lt;/a&gt; first appeared on &lt;a href="https://www.hackup.net"&gt;hackup.net&lt;/a&gt;.</description><author>hackup.net</author><pubDate>Sun, 08 Oct 2023 16:39:03 GMT</pubDate><guid isPermaLink="true">https://www.hackup.net/2023/10/sx-64-tiny-reset-count-reset-button-presses/</guid></item><item><title>Elon Musk</title><link>https://olshansky.info/book/elon_musk/</link><description>Olshansky's review of Elon Musk by Walter Isaacson</description><author>🦉 olshansky 🦁</author><pubDate>Sun, 08 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/book/elon_musk/</guid></item><item><title>Setting up k3s on Hetzner Cloud</title><link>https://ellie.wtf/notes/hetzner-k3s/</link><description>&lt;p&gt;I setup a HA k3s cluster for &lt;a href="https://ellie.wtf/projects/atuin/"&gt;Atuin&lt;/a&gt; recently!&lt;/p&gt;
&lt;p&gt;I'm using HA etcd, which means we need to run an odd number of "server" nodes, and obviously more than one of them. That makes 3 the minimum.&lt;/p&gt;
&lt;p&gt;The servers are all setup in a private network, in their own subnet, and are ARM instances. The firewall is setup to disallow almost all ingress, and most egress.&lt;/p&gt;
&lt;p&gt;Honestly I'm shocked at how easy this was. I may have messed something up I'm unaware of, but my experience with &lt;code&gt;kubeadm&lt;/code&gt; a few years back was &lt;em&gt;not&lt;/em&gt; as nice. Yay for k3s!&lt;/p&gt;
&lt;h3 id="sources"&gt;Sources&lt;/h3&gt;
&lt;p&gt;I read a bunch of stuff.&lt;/p&gt;
&lt;p&gt;This guide was a nice start: https://community.hetzner.com/tutorials/k3s-glusterfs-loadbalancer&lt;/p&gt;
&lt;p&gt;But the k3s docs are really good, and I mostly just read those: https://docs.k3s.io/&lt;/p&gt;
&lt;p&gt;I didn't use one of those (probably very good) automated hetzner k3s setup tools. I wanted to make sure I properly understood what was going on, and I've ran kubernetes on metal a few times in the past. Just with kubeadm, not k3s.&lt;/p&gt;
&lt;h2 id="servers"&gt;Servers&lt;/h2&gt;
&lt;p&gt;The first server! Note that k3s calls nodes running the control plane a "server", and other nodes an "agent". By default it allows the master nodes to also schedule normal workloads, which is probably fine for my use case.&lt;/p&gt;
&lt;p&gt;I've disabled the cloud controller as we're installing a special hetzner one, and also disabled local storage as I'm going to be using Longhorn. Be sure to pick a good token!&lt;/p&gt;
&lt;p&gt;Otherwise, as I enabled private networking on my Hetzner + want my cluster to use that, I've pointed flannel towards the private network interface.&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;curl&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; -sfL&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; https://get.k3s.io&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; |&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt; sh&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; -s&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; - server&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #D3869B;"&gt;	--cluster-init \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #D3869B;"&gt;    --disable-cloud-controller \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #D3869B;"&gt;    --disable&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; local-storage&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #D3869B;"&gt;    --node-name=&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;quot;$(&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;hostname&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; -f&lt;/span&gt;&lt;span style="color: #A89984;"&gt;)&amp;quot;&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #D3869B;"&gt;    --flannel-iface=enp7s0 \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #D3869B;"&gt;    --kubelet-arg=&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;cloud-provider=external&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #D3869B;"&gt;    --secrets-encryption \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #D3869B;"&gt;    --disable=traefik \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #D3869B;"&gt;    --token=CHANGE&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; ME&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Subsequent machines run a very similar command&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;curl&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; -sfL&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; https://get.k3s.io&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; |&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt; sh&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; -s&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; - server&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #D3869B;"&gt;	--server&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; SERVER ADDRESS&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #D3869B;"&gt;    --disable-cloud-controller \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #D3869B;"&gt;    --disable&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; local-storage&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #D3869B;"&gt;    --node-name=&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;quot;$(&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;hostname&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; -f&lt;/span&gt;&lt;span style="color: #A89984;"&gt;)&amp;quot;&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #D3869B;"&gt;    --flannel-iface=enp7s0 \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #D3869B;"&gt;    --kubelet-arg=&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;cloud-provider=external&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #D3869B;"&gt;    --secrets-encryption \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #D3869B;"&gt;    --disable=traefik \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #D3869B;"&gt;    --token=CHANGE&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; ME&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Please note:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Check that the network interface is correct&lt;/li&gt;
&lt;li&gt;Generate a token securely&lt;/li&gt;
&lt;li&gt;Check that cloud-provider=external is still required. In kubernetes v.1.29+ this may not be the case.&lt;/li&gt;
&lt;li&gt;Only the first server setup requires "cluster init". After that, you already have a cluster - no more init!&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote class="markdown-alert-note"&gt;
&lt;p&gt;The first time I got all the way to setting up Hetzner Cloud Controller until I saw that in their docs they require you to pass the &lt;code&gt;--kubelet-arg="cloud-provider=external"&lt;/code&gt; flag to each node.&lt;/p&gt;
&lt;p&gt;For most flags, you can just re-run the installer and it adjusts the config and restarts the node. For this flag in particular, if you miss it, you'll need to setup your cluster again. HCCM will only label nodes that were setup correctly from the very beginning, and unlabelled nodes won't work correctly with your LB.&lt;/p&gt;
&lt;p&gt;Some background on this. Kubernetes has a bunch of CCMs (cloud controller managers) that basically integrate k8s nicely with a cloud provider. In order to install an external CCM, we currently need to set the aforementioned flag. However, this has been deprecated for quite some time. It was originally intended to be removed in v1.24, though this has not yet happened.&lt;/p&gt;
&lt;p&gt;My understanding is that currently kubelet is bundled with some CCMs, so this flag allows you to use a non-bundled CCM. The future plans are to no longer bundle CCMs, making this argument redundant (hence deprecation)&lt;/p&gt;
&lt;p&gt;Issue if you're interested in learning more: https://github.com/kubernetes/kubernetes/issues/110018&lt;/p&gt;
&lt;p&gt;According to the linked PR, this may be included in v1.29. SO if you're running v1.29+, you might not want the cloud provider flag!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;At this point, you can run&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;kubectl&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; get nodes&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;from any of the machines setup, and get something like this:&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;NAME       STATUS   ROLES                       AGE    VERSION&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;server-1   Ready    control-plane,etcd,master   4m4s   v1.27.6+k3s1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;server-2   Ready    control-plane,etcd,master   47s    v1.27.6+k3s1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;server-3   Ready    control-plane,etcd,master   19s    v1.27.6+k3s1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And also check on the &lt;a href="https://docs.k3s.io/security/secrets-encryption" rel="external"&gt;secret encryption&lt;/a&gt; status with&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;k3s&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; secrets-encrypt status&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;Encryption Status: Enabled&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Current Rotation Stage: start&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Server Encryption Hashes: All hashes match&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Active  Key Type  Name&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;------  --------  ----&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt; *      AES-CBC   aescbckey&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sweet!&lt;/p&gt;
&lt;h2 id="access"&gt;Access&lt;/h2&gt;
&lt;p&gt;Before doing more setup, I wanted to setup &lt;code&gt;kubectl&lt;/code&gt; access from my laptop. Running commands directly from the nodes themselves felt gross.&lt;/p&gt;
&lt;p&gt;Once setup is fully complete I'll be setting up Tailscale (or maybe innernet) for access, but for now I'll just ssh port forward. You can get the kubeconfig via&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;cat&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; /etc/rancher/k3s/k3s.yaml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;on one of the nodes.&lt;/p&gt;
&lt;p&gt;A quick&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;ssh&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; -L&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; 6443:localhost:6443 root@a server ip&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and you're good to use &lt;code&gt;kubectl&lt;/code&gt; from your local device. Do setup something a little more robust though 😊&lt;/p&gt;
&lt;h2 id="hetzner-cloud-controller-manager"&gt;Hetzner Cloud Controller Manager&lt;/h2&gt;
&lt;p&gt;Try saying that 3 times really fast. Anyway &lt;a href="https://github.com/hetznercloud/hcloud-cloud-controller-manager" rel="external"&gt;hccm&lt;/a&gt; integrates our cluster with the hetzner cloud API, which means we can (stolen from the README):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;adds the server type to the &lt;code&gt;node.kubernetes.io/instance-type&lt;/code&gt; label, sets the external ipv4 and ipv6 addresses and deletes nodes from Kubernetes that were deleted from the Hetzner Cloud.&lt;/li&gt;
&lt;li&gt;makes Kubernetes aware of the failure domain of the server by setting the &lt;code&gt;topology.kubernetes.io/region&lt;/code&gt; and &lt;code&gt;topology.kubernetes.io/zone&lt;/code&gt; labels on the node.&lt;/li&gt;
&lt;li&gt;allows to use Hetzner Cloud Private Networks for your pods traffic.&lt;/li&gt;
&lt;li&gt;allows to use Hetzner Cloud Load Balancers with Kubernetes Services&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The hetzner blog post recommended just applying a manifest, &lt;em&gt;but&lt;/em&gt; the hccm docs recommend a helm chart. I've set it up with private networking enabled (why you would run this on a public network idk, maybe don't?)&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;helm&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; repo add hcloud https://charts.hetzner.cloud&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;helm&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; repo update hcloud&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You then need to set a k8s secret containing the hetzner cloud api token and network name (this is part of why I wanted to make sure secrets were encrypted at rest)&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;kubectl&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; -n&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; kube-system create secret generic hcloud&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; --from-literal=token=SOME&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; SECRET&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; --from-literal=network=NETWORK&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; NAME&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;helm&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; install hccm hcloud/hcloud-cloud-controller-manager&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; -n&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; kube-system&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; --set&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; networking.enabled=&lt;/span&gt;&lt;span style="color: #D3869B;"&gt;true --set&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; networking.clusterCIDR=10.42.0.0/16&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do note the setting of the clusterCIDR. If you haven't changed the &lt;a href="https://docs.k3s.io/cli/server#networking" rel="external"&gt;k3s defaults&lt;/a&gt;, 10.42.0.0/16 is good.&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;kubectl&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; logs&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; -n&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; kube-system deployment/hcloud-cloud-controller-manager&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Should now show some output, and&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;kubectl&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; describe node agent-1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;should show some extra info annotations:&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #B8BB26;"&gt;node.kubernetes.io/instance-type=cax21&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #B8BB26;"&gt;topology.kubernetes.io/region=fsn1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #B8BB26;"&gt;topology.kubernetes.io/zone=fsn1-dc14&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="agents"&gt;Agents&lt;/h2&gt;
&lt;p&gt;The default k3s behaviour is to actually schedule all deployments on nodes, so you may not need many of these (if that behaviour is OK with you)&lt;/p&gt;
&lt;p&gt;Setup is pretty similar to the servers! Just with less config. You'll need your token from before too&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;curl&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; -sfL&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; https://get.k3s.io&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; |&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt; sh&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; -s&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; - agent&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #D3869B;"&gt;	--server&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; SERVER ADDRESS&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #D3869B;"&gt;	--node-name=&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;quot;$(&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;hostname&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; -f&lt;/span&gt;&lt;span style="color: #A89984;"&gt;)&amp;quot;&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #D3869B;"&gt;	--flannel-iface=enp7s0 \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #D3869B;"&gt;	--kubelet-arg=&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;cloud-provider=external&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #D3869B;"&gt;	--token=CHANGE&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; ME&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can setup as many of these as you'd like&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;kubectl&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; get nodes&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;NAME       STATUS   ROLES                       AGE    VERSION&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;agent-1    Ready    &amp;lt;none&amp;gt;                      9s     v1.27.6+k3s1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;server-1   Ready    control-plane,etcd,master   100m   v1.27.6+k3s1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;server-2   Ready    control-plane,etcd,master   96m    v1.27.6+k3s1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;server-3   Ready    control-plane,etcd,master   96m    v1.27.6+k3s1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="ingress-with-traefik"&gt;Ingress with Traefik&lt;/h2&gt;
&lt;p&gt;Earlier, I setup the nodes so that traefik was disabled. But now I'm setting it up?&lt;/p&gt;
&lt;p&gt;Basically, the default k3s Traefik will use its own load balancer. There's nothing wrong with this really, but I wanted to make sure hccm was managing my LB. This means I get a proper cloud LB, with all the targets automatically managed by my cluster.&lt;/p&gt;
&lt;p&gt;It took a bit of messing to get working (I'd made some configuration errors detailed below)&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;helm&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; repo add traefik https://traefik.github.io/charts&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;helm&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; repo update&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I then created &lt;code&gt;traefik.values.yaml&lt;/code&gt;. I'm not going to paste the whole thing here - generally I get the default values, save them to file, and then edit as I see fit. It's also probably a good idea for you to set things up how you want!&lt;/p&gt;
&lt;p&gt;I will say that these annotations are very important on your service:&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #8EC07C;"&gt;service&lt;/span&gt;&lt;span style="color: #A89984;"&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #8EC07C;"&gt;  enabled&lt;/span&gt;&lt;span style="color: #A89984;"&gt;:&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #928374; font-style: italic;"&gt;  ## -- Single service is using `MixedProtocolLBService` feature gate.&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #928374; font-style: italic;"&gt;  ## -- When set to false, it will create two Service, one for TCP and one for UDP.&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #8EC07C;"&gt;  single&lt;/span&gt;&lt;span style="color: #A89984;"&gt;:&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #8EC07C;"&gt;  type&lt;/span&gt;&lt;span style="color: #A89984;"&gt;:&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; LoadBalancer&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #928374; font-style: italic;"&gt;  # -- Additional annotations applied to both TCP and UDP services (e.g. for cloud provider specific config)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #8EC07C;"&gt;  annotations&lt;/span&gt;&lt;span style="color: #A89984;"&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #8EC07C;"&gt;    load-balancer.hetzner.cloud/location&lt;/span&gt;&lt;span style="color: #A89984;"&gt;:&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; fsn1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #8EC07C;"&gt;    load-balancer.hetzner.cloud/name&lt;/span&gt;&lt;span style="color: #A89984;"&gt;:&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; lb&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #8EC07C;"&gt;    load-balancer.hetzner.cloud/use-private-ip&lt;/span&gt;&lt;span style="color: #A89984;"&gt;: &amp;quot;&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;true&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First we set the location of the lb. Give it a name. And then &lt;em&gt;tell it to use private ips&lt;/em&gt;. I did not have this option at first, and my load balancer didn't work! All the targets were unhealthy.&lt;/p&gt;
&lt;p&gt;By default, hccm was only adding the public IP addresses to the LB. My firewall was blocking it (no public ingress straight to nodes), so nothing was routing ok. With this change, all was well 😇&lt;/p&gt;
&lt;p&gt;I'm also using Cloudflare to terminate my SSL. I'll probably setup cert-manager at some point, but I am trying to keep my cluster as stateless as possible. Plus it's nice/easy to terminate SSL at the edge.&lt;/p&gt;
&lt;h2 id="next-steps"&gt;Next steps&lt;/h2&gt;
&lt;p&gt;This is now at the point where I can deploy stateless services + point DNS at them with Cloudflare!&lt;/p&gt;
&lt;p&gt;There's a bunch I'd like to do next, although things are working fairly well now&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Setup storage with Longhorn, as right now we have no storage&lt;/li&gt;
&lt;li&gt;Monitoring setup&lt;/li&gt;
&lt;li&gt;Cloud init for automated agent setup&lt;/li&gt;
&lt;li&gt;Easy VPN access&lt;/li&gt;
&lt;li&gt;Extra Security hardening&lt;/li&gt;
&lt;/ol&gt;</description><author>Ellie's Notes</author><pubDate>Sun, 08 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ellie.wtf/notes/hetzner-k3s/</guid></item><item><title>Zipdl</title><link>https://mfashby.net/posts/2023-10-07-zipdl/</link><description/><author>Home on mfashby.net</author><pubDate>Sun, 08 Oct 2023 01:28:16 GMT</pubDate><guid isPermaLink="true">https://mfashby.net/posts/2023-10-07-zipdl/</guid></item><item><title>Complex Simulation: Artifacts of a Russian spy</title><link>https://liza.io/complex-simulation-artifacts-of-a-russian-spy/</link><description>&lt;p&gt;I spent a year in Gothenburg after many years in Stockholm. The neighborhood I lived in for most of that time was a very quiet residential area with a small square. In the square was a Thai lunch place. As with many lunch offers in Sweden, the place offered free coffee with lunch. You&amp;rsquo;d come up to a self-serve coffee counter and pour yourself a cup.&lt;/p&gt;</description><author>Liza Shulyayeva</author><pubDate>Sun, 08 Oct 2023 01:01:33 GMT</pubDate><guid isPermaLink="true">https://liza.io/complex-simulation-artifacts-of-a-russian-spy/</guid></item><item><title>DeepRacer Day</title><link>https://tig.pt/deepracer-day/</link><description>&lt;p&gt;Today I was part of the team organising a &lt;a href="https://www.meetup.com/sixt-tech-hub-lisbon/events/295023584/?ref=tig.pt"&gt;DeepRacer Day&lt;/a&gt; with SIXT. &lt;/p&gt;&lt;p&gt;It is awesome to share some of the experiences and help others start on the Cloud Journey.&lt;/p&gt;&lt;p&gt;As I won 1st place on AWS DeepRacer Madrid 2022, tecRacer kind of nominated me as the DeepRacer guy.&lt;/p&gt;&lt;p&gt;Once&lt;/p&gt;</description><author>Tiago Rodrigues</author><pubDate>Sun, 08 Oct 2023 00:52:00 GMT</pubDate><guid isPermaLink="true">https://tig.pt/deepracer-day/</guid></item><item><title>A Better US Political Party for Conservatives</title><link>https://synystron.substack.com/p/a-better-us-political-party-for-conservatives</link><description>The US needs a major conservative party that is opposed to &amp; FORBIDS among its candidates &amp; officers:</description><author>Synystron Synlogica</author><pubDate>Sat, 07 Oct 2023 18:58:44 GMT</pubDate><guid isPermaLink="true">https://synystron.substack.com/p/a-better-us-political-party-for-conservatives</guid></item><item><title>GitHub Copilot review</title><link>https://zackproser.com/blog/github-copilot-review</link><description>I got Copilot access for free as an active GitHub open-source maintainer, but would I pay for it?</description><author>Zachary Proser</author><pubDate>Sat, 07 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/github-copilot-review</guid></item><item><title>Why your AI dev tool startup is failing with developers</title><link>https://zackproser.com/blog/how-developers-evaluate-ai-coding-tools</link><description>These common mistakes are dooming your AI-assisted developer tooling startup to failure. Don't make them</description><author>Zachary Proser</author><pubDate>Sat, 07 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/how-developers-evaluate-ai-coding-tools</guid></item><item><title>Moving Marginalia to a New Server</title><link>https://www.marginalia.nu/log/90-new-server-design/</link><description>&lt;p&gt;So the search engine is moving to a new server soon, thanks to the generous grant
&lt;a href="https://www.marginalia.nu/log/88-futo-grant/"&gt;mentioned recently&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you visit search.marginalia.nu now, it may or may not use the old or new server. It&amp;rsquo;ll be like this for
a while, since I need them both for testing and maintenance type work.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll also apologize if this post is a bit chaotic. It is a reflection of a very chaotic couple of weeks that
apart from setting up this migration also involved a very short notice invitation for a
presentation at &lt;a href="https://opensearchfoundation.org/en/events-osf/5th-international-open-search-symposium-ossym2023/"&gt;ossym23&lt;/a&gt;.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Sat, 07 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/90-new-server-design/</guid></item><item><title>How to Reduce Your PostgreSQL Database Size</title><link>https://dylanpaulus.com/posts/2023/how-to-reduce-your-postgresql-database-size/</link><description>&lt;p&gt;I had the pleasure of working with &lt;a href="https://www.timescale.com/"&gt;Timescale&lt;/a&gt; to&lt;br /&gt;
write an article on how to reduce your PostgreSQL database size.&lt;/p&gt;
&lt;p&gt;We cover a range of topics including:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;What are pages and tuples&lt;/li&gt;
  &lt;li&gt;How table bloat occurs&lt;/li&gt;
  &lt;li&gt;Why we should vacuum our tables&lt;/li&gt;
  &lt;li&gt;Timescale compression&lt;/li&gt;
  &lt;li&gt;Retention policies&lt;/li&gt;
  &lt;li&gt;And more!&lt;/li&gt;
&lt;/ul&gt;</description><author>Dylan Paulus' Blog</author><pubDate>Sat, 07 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://dylanpaulus.com/posts/2023/how-to-reduce-your-postgresql-database-size/</guid></item><item><title>SciPy distances... up to 200x faster with AVX-512 &amp;amp; SVE 📏</title><link>https://ashvardanian.com/posts/simsimd-faster-scipy/</link><description>AVX-512 and Arm SVE bring masked loads and native f16 math to vector distances. SimSIMD exploits both, running up to 200x faster than SciPy on modern CPUs.</description><author>Ash's Blog</author><pubDate>Sat, 07 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ashvardanian.com/posts/simsimd-faster-scipy/</guid></item><item><title>Getting into Photography on a Budget in 2023</title><link>https://thomashunter.name/posts/2023-10-07-photography-on-a-budget-in-2023</link><author>Thomas Hunter II</author><pubDate>Sat, 07 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://thomashunter.name/posts/2023-10-07-photography-on-a-budget-in-2023</guid></item><item><title>Adapting Gilles Castel’s LaTeX snippets for Typst</title><link>https://www.jskherman.com/blog/typst-snippets/</link><description>My attempt at converting Gilles Castel&amp;rsquo;s LaTeX snippets to Typst on Neovim and VS Code to make writing math faster just like in Castel&amp;rsquo;s original post.</description><author>jskherman</author><pubDate>Sat, 07 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.jskherman.com/blog/typst-snippets/</guid></item><item><title>The Photo Game: Grass</title><link>https://andrewbridges.org/the-photo-game-grass/</link><description>We planted some of these grasses in our yard this summer. They're doing okay but not thriving–at least not yet.</description><author>Show Notes</author><pubDate>Fri, 06 Oct 2023 21:32:56 GMT</pubDate><guid isPermaLink="true">https://andrewbridges.org/the-photo-game-grass/</guid></item><item><title>New Bump Mural</title><link>https://lagomor.ph/indieweb/20231006/1230-note/</link><description>&lt;p&gt;Walked down a street in an opposite direction then I normally do and discovered a grand new mural on the opposite wall.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A Mural of man made of moss, mushrooms, and flowers" src="https://images.lagomor.ph/archive/mural.JPG" /&gt;&lt;/p&gt;</description><author>Home on Lagomorph</author><pubDate>Fri, 06 Oct 2023 21:30:00 GMT</pubDate><guid isPermaLink="true">https://lagomor.ph/indieweb/20231006/1230-note/</guid></item><item><title>Markdown is The New HTML (and Web)</title><link>https://synystron.substack.com/p/markdown-is-the-new-html-and-web</link><description>Markdown is the new HTML</description><author>Synystron Synlogica</author><pubDate>Fri, 06 Oct 2023 18:33:31 GMT</pubDate><guid isPermaLink="true">https://synystron.substack.com/p/markdown-is-the-new-html-and-web</guid></item><item><title>Suggested readings week 40/2023</title><link>https://www.stefanocosta.org/suggested-readings-week-40-2023/</link><description>&lt;hr /&gt;&lt;h3 id="the-data-delusion"&gt;&lt;a href="https://www.newyorker.com/magazine/2023/04/03/the-data-delusion?ref=stefanocosta.org" rel="nofollow noreferrer noopener"&gt;The Data Delusion&lt;/a&gt;&lt;/h3&gt;&lt;blockquote&gt;Surveying a million books on ten thousand shelves is a nightmare&lt;/blockquote&gt;&lt;p&gt;This is J. C. R. Licklider, a chief engineer and pioneer of the early Internet, in 1965. He wrote the book Libraries of the Future fter two years of research, in which he envisioned a future&lt;/p&gt;</description><author>Food for Thought (by Stefano Costa)</author><pubDate>Fri, 06 Oct 2023 15:30:07 GMT</pubDate><guid isPermaLink="true">https://www.stefanocosta.org/suggested-readings-week-40-2023/</guid></item><item><title>Creativity Is A Muscle</title><link>https://martinrue.com/creativity-muscle/</link><description>Creativity is a muscle. Progressively overload it so it keeps growing, and whatever you do don't forget to rest it.</description><author>Martin Rue</author><pubDate>Fri, 06 Oct 2023 15:00:00 GMT</pubDate><guid isPermaLink="true">https://martinrue.com/creativity-muscle/</guid></item><item><title>AI Generated QR Codes with Stable Diffusion and ControlNet</title><link>https://reticulated.net/dailyai/ai-generated-qr-codes-with-stable-diffusion/</link><description>A look at generating creative QR codes with QRCodeMonster</description><author>Reticulated</author><pubDate>Fri, 06 Oct 2023 13:35:05 GMT</pubDate><guid isPermaLink="true">https://reticulated.net/dailyai/ai-generated-qr-codes-with-stable-diffusion/</guid></item><item><title>A Bit of SaaS Weekly: The bigger they are...</title><link>https://ethanmick.com/a-bit-of-saas-weekly-the-bigger-they-are/</link><description>&lt;p&gt;This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by &lt;a href="https://ethanmick.com/"&gt;Ethan Mick&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Up here in the northern hemisphere, the leaves are starting to change, and the air is getting colder. We&amp;apos;ve entered the 4th quarter of the year, and there&lt;/p&gt;</description><author>Build SaaS with Ethan</author><pubDate>Fri, 06 Oct 2023 13:00:55 GMT</pubDate><guid isPermaLink="true">https://ethanmick.com/a-bit-of-saas-weekly-the-bigger-they-are/</guid></item><item><title>Getting Started Building a Data-driven Business</title><link>https://jorin.me/gettting-started-building-a-data-driven-business/</link><description>Making effective use of data is non-trivial. Let me share what worked for us.</description><author>jorin.me</author><pubDate>Fri, 06 Oct 2023 09:00:00 GMT</pubDate><guid isPermaLink="true">https://jorin.me/gettting-started-building-a-data-driven-business/</guid></item><item><title>Trimming video screenshots</title><link>https://xenodium.com/trimming-video-screenshots</link><description>&lt;p&gt;A quick one… I recently wanted to trim the black borders around a video screenshot. While I could use an image editor to manually select and trim, I wondered if there was an &lt;a href="https://imagemagick.org/"&gt;imagemagick&lt;/a&gt; trick somewhere out there for such a thing… and of course there was:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.bash"&gt;magick convert -fuzz 3% -define trim:percent-background=0% -trim +repage path/to/input.png path/to/output.png
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pretty neat. It does the job, but I won't remember it next time. May as well make another &lt;a href="https://github.com/xenodium/dwim-shell-command"&gt;dwim-shell-command&lt;/a&gt; function out of it and conveniently invoke from Emacs via a memorable name plus fuzzy search.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/trimming-video-screenshots/trim.gif" /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(defun dwim-shell-commands-image-trim-borders ()
  &amp;quot;Trim image(s) border (useful for video screenshots).&amp;quot;
  (interactive)
  (dwim-shell-command-on-marked-files
   &amp;quot;Trim image border&amp;quot;
   &amp;quot;magick convert -fuzz 3% -define trim:percent-background=0% -trim +repage '&amp;lt;&amp;lt;f&amp;gt;&amp;gt;' '&amp;lt;&amp;lt;fne&amp;gt;&amp;gt;_trimmed.&amp;lt;&amp;lt;e&amp;gt;&amp;gt;'&amp;quot;
   :utils &amp;quot;magick&amp;quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While the screenshot I've just used was a little blurry, it's from the movie &lt;a href="https://www.imdb.com/title/tt1104001/"&gt;Tron Legacy&lt;/a&gt;, and it features Emacs eshell. This is old news, though &lt;a href="https://irreal.org/blog/?p=9573"&gt;well covered&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/trimming-video-screenshots/eshell_trimmed.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dwim-shell-commands-image-trim-borders&lt;/code&gt; is now &lt;a href="https://github.com/xenodium/dwim-shell-command/commit/5bed2f6b40761db4913c8d8f58bb147c71a9ceb7"&gt;added to dwim-shell-commands.el&lt;/a&gt;&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Fri, 06 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/trimming-video-screenshots</guid></item><item><title>Displaying image details in mode line</title><link>https://xenodium.com/displaying-image-details-in-mode-line</link><description>&lt;p&gt;A benefit of running Emacs as a GUI app, is that you can view images from your beloved editor. This is super handy to take a quick peek at any image.&lt;/p&gt;
&lt;p&gt;Sometimes, I'd like a little more than just viewing the image. I'd like to see basic image details like type, dimensions, and file size. The &lt;a href="https://imagemagick.org/"&gt;imagemagick&lt;/a&gt; &lt;code&gt;identify&lt;/code&gt; utility is pretty handy for that.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;identify -format &amp;quot;%m %wx%h %b&amp;quot; path/to/image.png
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I could easily invoke &lt;code&gt;shell-command&lt;/code&gt; for this or even create a &lt;a href="https://github.com/xenodium/dwim-shell-command"&gt;dwim-shell-command&lt;/a&gt; function (maybe I will), but if this info was proactively displayed in the mode line, I wouldn't have to fetch it myself.&lt;/p&gt;
&lt;p&gt;Since I know I can use the &lt;code&gt;identify&lt;/code&gt; command for this, I may as well see if I can plug it into the mode line.&lt;/p&gt;
&lt;p&gt;Turns out this wasn't too bad by setting &lt;code&gt;setting mode-line-format&lt;/code&gt;. I added a little logic to only include image details while in &lt;code&gt;image-mode&lt;/code&gt; and rely on &lt;code&gt;process-lines&lt;/code&gt; to fetch the details. This function returns a list, which is a happy coincidence since &lt;code&gt;mode-line-format&lt;/code&gt; also expects a list.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(setq-default mode-line-format
              '(&amp;quot; &amp;quot;
                mode-line-front-space
                mode-line-client
                mode-line-frame-identification
                mode-line-buffer-identification
                (:eval
                 (when (eq major-mode 'image-mode)
                   ;; Needs imagemagick installed.
                   (process-lines &amp;quot;identify&amp;quot; &amp;quot;-format&amp;quot; &amp;quot;[%m %wx%h %b]&amp;quot; (buffer-file-name))))
                &amp;quot; &amp;quot;
                mode-line-position
                (vc-mode vc-mode)
                (multiple-cursors-mode mc/mode-line)
                &amp;quot; &amp;quot; mode-line-modes
                mode-line-end-spaces))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/displaying-image-details-in-mode-line/buddies.png" /&gt;&lt;/p&gt;
&lt;p&gt;I'd love to hear if there's a pure elisp alternative (&lt;a href="https://indieweb.social/@xenodium"&gt;mastodon&lt;/a&gt;/&lt;a href="https://twitter.com/xenodium"&gt;twitter&lt;/a&gt;). I gave &lt;code&gt;(image-size (image-get-display-property) :pixels)&lt;/code&gt; a try, but that seemed to return the display size in buffer rather than actual file size.&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Fri, 06 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/displaying-image-details-in-mode-line</guid></item><item><title>Creating an iCloud account (via tart VM)</title><link>https://xenodium.com/creating-icloud-test-accounts</link><description>&lt;p&gt;&lt;em&gt;UPDATE: This method no longer works for creating iCloud accounts. I'd love to know if you find an alternative. Please let me know.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I wanted an additional @icloud.com account for myself. My first thought was to head over to &lt;a href="https://developer.apple.com"&gt;https://developer.apple.com&lt;/a&gt; and create a new account, but that requires an existing email address. I wanted an actual @icloud.com email address.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/creating-icloud-test-accounts/web.png" /&gt;&lt;/p&gt;
&lt;p&gt;My next thought was to create a new account using the iOS simulator, but that complained about creating too many accounts already. Strange, as I hadn't created any.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/creating-icloud-test-accounts/iphone.png" /&gt;&lt;/p&gt;
&lt;p&gt;I could create an account from macOS settings itself, though that would require logging out my current account (and the syncing implications). To get around that, I could maybe create a temporary macOS user. Instead, I somewhat revisited the simulator route and looked for a VM option to run macOS. This gave me an excuse to play with VM options on macOS.&lt;/p&gt;
&lt;p&gt;I had been meaning to check out &lt;a href="https://github.com/lima-vm/lima"&gt;lima&lt;/a&gt; as per Hacker News's &lt;a href="https://news.ycombinator.com/item?id=36668964"&gt;Lima: A nice way to run Linux VMs on Mac&lt;/a&gt;. The Hacker News's &lt;a href="https://news.ycombinator.com/item?id=36668964"&gt;thread&lt;/a&gt; has a handful of great recommendations. Amongst them, &lt;a href="https://github.com/cirruslabs/tart/"&gt;tart&lt;/a&gt; (macOS and Linux VMs on Apple Silicon) stood out, as it also gave me the Mac on Mac option.&lt;/p&gt;
&lt;p&gt;Installing &lt;code&gt;tart&lt;/code&gt; via &lt;a href="https://brew.sh/"&gt;Homebrew&lt;/a&gt; followed the typical &lt;code&gt;brew&lt;/code&gt; command… a breeze via my trusty Emacs &lt;a href="https://www.masteringemacs.org/article/complete-guide-mastering-eshell"&gt;eshell&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.bash"&gt;brew install cirruslabs/cli/tart
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cloning a VM image, while straightforward, it did take a little while for the chunky download:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.bash"&gt;tart clone ghcr.io/cirruslabs/macos-sonoma-base:latest sonoma-base
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running the macOS Sonoma VM was a breeze:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.bash"&gt;tart run sonoma-base
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;…and with that, I got a full (and disposable) macOS VM I can use to create another @icloud.com account:&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/creating-icloud-test-accounts/sonoma1.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/creating-icloud-test-accounts/sonoma2.png" /&gt;&lt;/p&gt;
&lt;p&gt;While there may be simpler options out there to create an @icloud.com account (please do let me know &lt;a href="https://indieweb.social/@xenodium"&gt;mastodon&lt;/a&gt;/&lt;a href="https://twitter.com/xenodium"&gt;twitter&lt;/a&gt;), the VM did the job. I'd been meaning to find a low friction mechanism to run VMs for a different reason, but that's a post for another time.&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Fri, 06 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/creating-icloud-test-accounts</guid></item><item><title>Unit Testing Cross-Fetch, TypeScript and Jest</title><link>https://aknapen.nl/blog/20231006-unit-testing-cross-fetch-typescript-and-jest/</link><description>Writing unit-tests for a library which overwrites the global fetch function can be a bit tricky. This post will show you how to do it.
The Problem When using cross-fetch in TypeScript, you will most likely import it like this:
import crossFetch from &amp;quot;cross-fetch&amp;quot;  This is because the cross-fetch package is not the default export, but rather a named export. However, when you want to mock the cross-fetch, you can&amp;rsquo;t use the jest.</description><author>Adriaan Knapen</author><pubDate>Fri, 06 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://aknapen.nl/blog/20231006-unit-testing-cross-fetch-typescript-and-jest/</guid></item><item><title>Note 117</title><link>https://qubyte.codes/notes/1696546535205</link><description>&lt;p&gt;I’ve only been away on business since Sunday, but I miss my family &lt;em&gt;so&lt;/em&gt; much. Thank goodness I’m on the way back.&lt;/p&gt;</description><author>Qubyte Codes</author><pubDate>Fri, 06 Oct 2023 01:55:35 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/notes/1696546535205</guid></item><item><title>Attack Lab</title><link>https://web.navan.dev/posts/2023-10-05-attack-lab.html</link><description>Walkthrough of Attack Lab Phases 1-4 for CSCI 2400 Computer Systems</description><author>Navan's Archive</author><pubDate>Thu, 05 Oct 2023 23:01:00 GMT</pubDate><guid isPermaLink="true">https://web.navan.dev/posts/2023-10-05-attack-lab.html</guid></item><item><title>Introducing "Ednevsky Blog"</title><link>https://www.ednevsky.blog/p/introducing-ednevsky-blog</link><description>I'm relaunching my blog &amp;#8212; read below to see what's next!</description><author>Ednevsky Blog</author><pubDate>Thu, 05 Oct 2023 20:59:02 GMT</pubDate><guid isPermaLink="true">https://www.ednevsky.blog/p/introducing-ednevsky-blog</guid></item><item><title>Note 116</title><link>https://qubyte.codes/notes/1696517473287</link><description>&lt;p&gt;Toronto at dusk.&lt;/p&gt;

    &lt;img alt="The Toronto skyline as viewed from the roof of the Shopify offices. The CN tower is in frame." src="https://qubyte.codes/images/1696517405820.jpeg" /&gt;</description><author>Qubyte Codes</author><pubDate>Thu, 05 Oct 2023 17:51:13 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/notes/1696517473287</guid></item><item><title>Sobre la pérdida</title><link>https://forjandomivida.substack.com/p/about-loss</link><description>C&amp;#243;mo dejar ir</description><author>The solver</author><pubDate>Thu, 05 Oct 2023 13:00:14 GMT</pubDate><guid isPermaLink="true">https://forjandomivida.substack.com/p/about-loss</guid></item><item><title>Ripples Across the Web</title><link>http://evantravers.com/articles/2023/10/05/ripples-across-the-web/</link><description>&lt;em&gt;
  &lt;svg height="16" viewBox="0 0 24 24" width="16" xmlns="http://www.w3.org/2000/svg"&gt;
    # &lt;path d="M 4 4.44 v 2.83 c 7.03 0 12.73 5.7 12.73 12.73 h 2.83 c 0 -8.59 -6.97 -15.56 -15.56 -15.56 Z m 0 5.66 v 2.83 c 3.9 0 7.07 3.17 7.07 7.07 h 2.83 c 0 -5.47 -4.43 -9.9 -9.9 -9.9 Z M 6.18 15.64 A 2.18 2.18 0 0 1 6.18 20 A 2.18 2.18 0 0 1 6.18 15.64" fill="currentColor"&gt;&lt;/path&gt;
  &lt;/svg&gt;
  This is an RSS-only post. It's a secret! Read more about &lt;a href="/rss-club"&gt;RSS Club&lt;/a&gt;.
&lt;/em&gt;
&lt;p&gt;A fun sequence of events…&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I saw a novel question on my For You page, &lt;a href="https://twitter.com/evantravers/status/1688651100973375488"&gt;searched Google domains and responded&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Over the following week I got notifications as Benten built &lt;a href="https://footer.design"&gt;footer.design&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Yesterday &lt;a href="https://robinrendle.com/notes/footer.design-/"&gt;Robin noted it&lt;/a&gt;. I saw it on RSS and giggled.&lt;/li&gt;
&lt;li&gt;sidebar.io &lt;a href="https://sidebar.io/date/2023-10-05"&gt;reported it today&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Benten did all the work, but it &lt;strong&gt;is&lt;/strong&gt; fun being a tiny part of pushing it over the edge.&lt;/p&gt;</description><author>trv.rs</author><pubDate>Thu, 05 Oct 2023 11:36:00 GMT</pubDate><guid isPermaLink="true">http://evantravers.com/articles/2023/10/05/ripples-across-the-web/</guid></item><item><title>Serialization woes</title><link>https://river.me/blog/serialization-woes/</link><description>My journey of encoding pentominoes on surfaces into permalinks</description><author>River Writes - A MediaWiki Blog</author><pubDate>Thu, 05 Oct 2023 09:29:07 GMT</pubDate><guid isPermaLink="true">https://river.me/blog/serialization-woes/</guid></item><item><title>Throughput Theater</title><link>https://jodavaho.io/posts/dev-throughput-theater-1.html</link><description>&lt;blockquote&gt;
&lt;p&gt;I regret to inform you that member initialization lists will not make your program noticeably faster.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The point of this post is to vent, so it is highly opinionated.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Style-based performance optimizations are a waste of time&lt;/li&gt;
&lt;li&gt;Good fundamentals are more important than micro-optimizations&lt;/li&gt;
&lt;li&gt;Compile and test times are vastly underrated&lt;/li&gt;
&lt;li&gt;For some reason, code / function &lt;em&gt;generalization&lt;/em&gt; is valued higher than &lt;em&gt;ergonomics&lt;/em&gt; or &lt;em&gt;real performance&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I form this opinion based on experiences with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The near-constant debate over style, move semantics, constructor initialization lists, and other micro-optimizations&lt;/li&gt;
&lt;li&gt;Making small changes and seeing large performance gains, sometimes 1,000-100,000x by:
&lt;ul&gt;
&lt;li&gt;Using hardware more efficiently&lt;/li&gt;
&lt;li&gt;Reframing the core problem&lt;/li&gt;
&lt;li&gt;Using the right data structures&lt;/li&gt;
&lt;li&gt;Using the right algorithms&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In short, if you&amp;rsquo;re worried about the performance problems with initialize+copy vs std::move, you&amp;rsquo;re either doing something very wrong or you&amp;rsquo;re down to the last 0.1% of performance improvements.&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Thu, 05 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/dev-throughput-theater-1.html</guid></item><item><title>Implementing growing arrays for C</title><link>https://xnacly.me/posts/2023/growing-array-c/</link><description>Implementing a go like slice type for and in c</description><author>xnacly - blog</author><pubDate>Thu, 05 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xnacly.me/posts/2023/growing-array-c/</guid></item><item><title>Quitting while you're ahead</title><link>https://tiltingatwindmills.dev/quitting-while-youre-ahead/</link><description>I often wonder, how many actors are there who quietly disappeared beacause they
made a bunch of money from one lucky success? Hollywood…</description><author>Tilting at Windmills</author><pubDate>Thu, 05 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://tiltingatwindmills.dev/quitting-while-youre-ahead/</guid></item><item><title>An alternative frontend for Haskell?</title><link>https://gilmi.me/post/2023/10/05/haskell-alternative-frontend</link><description>What if we were to build an alternative syntax for Haskell?</description><author>λm.me</author><pubDate>Thu, 05 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://gilmi.me/post/2023/10/05/haskell-alternative-frontend</guid></item><item><title>Go database driver overhead on insert-heavy workloads</title><link>http://notes.eatonphil.com/2023-10-05-go-database-sql-overhead-on-insert-heavy-workloads.html</link><description>&lt;p&gt;The most popular SQLite and PostgreSQL database drivers in Go are
(roughly) 20-76% slower than alternative Go drivers on insert-heavy
benchmarks of mine. So if you are bulk-inserting data with Go (and
potentially also bulk-retrieving data with Go), you may want to
consider the driver carefully. And you may want to consider avoiding
&lt;code&gt;database/sql&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Some driver authors have
&lt;a href="https://github.com/lib/pq/issues/771"&gt;noted&lt;/a&gt; and
&lt;a href="https://github.com/ClickHouse/clickhouse-go/tree/main#benchmark"&gt;benchmarked&lt;/a&gt;
issues with
&lt;a href="https://github.com/jackc/pgx#choosing-between-the-pgx-and-databasesql-interfaces"&gt;database/sql&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So it may be the case that &lt;code&gt;database/sql&lt;/code&gt; is responsible for some of
this overhead. And indeed the variations between drivers in this post
will be demonstrated by using &lt;code&gt;database/sql&lt;/code&gt; and avoiding it. This post
won't specifically prove that the variation is due to the
&lt;code&gt;database/sql&lt;/code&gt; interface. But that doesn't change the premise.&lt;/p&gt;
&lt;p class="note"&gt;
  Not covered in this post but something to consider:
  JetBrains &lt;a href="https://blog.jetbrains.com/go/2023/04/27/comparing-db-packages/"&gt;has
  suggested&lt;/a&gt; that other frontends like sqlc, sqlx, and GORM do
  worse than &lt;code&gt;database/sql&lt;/code&gt;.
&lt;/p&gt;&lt;p&gt;This post is built on the workload, environment, libraries, and
methodology in my &lt;a href="https://github.com/eatonphil/databases-intuition"&gt;databases-intuition repo on
GitHub&lt;/a&gt;. See the
repo for details that will help you reproduce or correct me.&lt;/p&gt;
&lt;h3 id="insert-workload"&gt;INSERT workload&lt;/h3&gt;&lt;p&gt;In this workload, the data is random and there are no indexes. Neither
of these aspects matter for this post though because we're comparing
behavior within the same database among different drivers. This was
just a workload I already had.&lt;/p&gt;
&lt;p&gt;Two different data sizes are tested:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;10M rows with 16 columns, each column is 32 bytes&lt;/li&gt;
&lt;li&gt;10M rows with 3 columns, each column is 8 bytes&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each test is run 10 times and we record median, standard deviation,
min, max and throughput.&lt;/p&gt;
&lt;h3 id="sqlite"&gt;SQLite&lt;/h3&gt;&lt;p&gt;Both variations presented here load 10M rows using a single prepared
statement called for each row within a single transaction.&lt;/p&gt;
&lt;p&gt;The most popular driver is
&lt;a href="https://github.com/mattn/go-sqlite3"&gt;mattn/go-sqlite3&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It is roughly 20-40% slower than another driver that avoids
&lt;code&gt;database/sql&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;10M Rows, 16 columns, each column 32 bytes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Timing:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;56&lt;/span&gt;.53&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.26s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;55&lt;/span&gt;.05s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;59&lt;/span&gt;.62s
Throughput:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;176&lt;/span&gt;,893.65&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;,853.90&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;167&lt;/span&gt;,719.97&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;181&lt;/span&gt;,646.02&lt;span class="w"&gt; &lt;/span&gt;rows/s
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;10M Rows, 3 columns, each column 8 bytes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Timing:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;.92&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.25s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;.69s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;16&lt;/span&gt;.67s
Throughput:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;628&lt;/span&gt;,044.37&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;,703.92&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;599&lt;/span&gt;,852.91&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;637&lt;/span&gt;,435.60&lt;span class="w"&gt; &lt;/span&gt;rows/s
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The other driver I tested is my own fork of
&lt;a href="https://github.com/bvinc/go-sqlite-lite"&gt;bvinc/go-sqlite-lite&lt;/a&gt; called
&lt;a href="https://github.com/eatonphil/gosqlite"&gt;eatonphil/gosqlite&lt;/a&gt;. I forked
it because it is unmaintained and I wanted to bring it up-to-date for
tests like this.&lt;/p&gt;
&lt;p&gt;10M Rows, 16 columns, each column 32 bytes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Timing:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;45&lt;/span&gt;.51&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.70s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;43&lt;/span&gt;.72s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;45&lt;/span&gt;.93s
Throughput:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;219&lt;/span&gt;,729.65&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;,447.56&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;217&lt;/span&gt;,742.98&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;228&lt;/span&gt;,711.51&lt;span class="w"&gt; &lt;/span&gt;rows/s
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;10M Rows, 3 columns, each column 8 bytes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Timing:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.44&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.20s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.02s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.68s
Throughput:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;957&lt;/span&gt;,939.60&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;,879.43&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;936&lt;/span&gt;,114.60&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;998&lt;/span&gt;,426.62&lt;span class="w"&gt; &lt;/span&gt;rows/s
&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="postgresql"&gt;PostgreSQL&lt;/h3&gt;&lt;p&gt;Both variations presented use PostgreSQL's &lt;a href="https://www.postgresql.org/docs/current/sql-copy.html"&gt;&lt;code&gt;COPY
FROM&lt;/code&gt;&lt;/a&gt;
support. This is significantly faster for PostgreSQL than doing the
prepared statement we do in
SQLite. (&lt;a href="https://github.com/eatonphil/databases-intuition#postgresql-prepared-insert"&gt;Here&lt;/a&gt;
are my results for doing prepared statement INSERTs in PostgreSQL if
you are curious.)&lt;/p&gt;
&lt;p&gt;The most popular PostgreSQL driver is
&lt;a href="https://github.com/lib/pq"&gt;lib/pq&lt;/a&gt;. The &lt;a href="https://github.com/lib/pq/issues/771"&gt;performance
issues&lt;/a&gt; with lib/pq are
&lt;a href="https://github.com/jackc/pgx#choosing-between-the-pgx-and-databasesql-interfaces"&gt;well-known&lt;/a&gt;,
and the &lt;a href="https://github.com/lib/pq#status"&gt;repo itself&lt;/a&gt; is marked as
no longer developed.&lt;/p&gt;
&lt;p&gt;It is roughly 44-76% slower than an alternative driver that avoids
&lt;code&gt;database/sql&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;10M Rows, 16 columns, each column 32 bytes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Timing:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;104&lt;/span&gt;.53&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.40s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;102&lt;/span&gt;.57s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;110&lt;/span&gt;.08s
Throughput:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;95&lt;/span&gt;,665.37&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;,129.25&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;90&lt;/span&gt;,847.08&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;97&lt;/span&gt;,490.96&lt;span class="w"&gt; &lt;/span&gt;rows/s
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;10M Rows, 3 columns, each column 8 bytes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Timing:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;.16&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.43s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;.44s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;.80s
Throughput:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;,225,986.47&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;66&lt;/span&gt;,631.53&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;,136,581.82&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;,343,441.37&lt;span class="w"&gt; &lt;/span&gt;rows
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The other driver I tested is
&lt;a href="https://github.com/jackc/pgx"&gt;jackc/pgx&lt;/a&gt;, without &lt;code&gt;database/sql&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;10M Rows, 16 columns, each column 32 bytes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Timing:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;46&lt;/span&gt;.54&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.60s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;44&lt;/span&gt;.09s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;49&lt;/span&gt;.51s
Throughput:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;214&lt;/span&gt;,869.42&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;,265.10&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;201&lt;/span&gt;,991.37&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;226&lt;/span&gt;,801.07&lt;span class="w"&gt; &lt;/span&gt;rows/s
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;10M Rows, 3 columns, each column 8 bytes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Timing:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;.20&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.44s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.71s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;.96s
Throughput:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;,923,722.79&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;156&lt;/span&gt;,820.46&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;,676,894.32&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;,124,966.60&lt;span class="w"&gt; &lt;/span&gt;rows/
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The discrepancies here are even greater than with the different SQLite
drivers.&lt;/p&gt;
&lt;h3 id="workloads-with-small-resultset"&gt;Workloads with small resultset&lt;/h3&gt;&lt;p&gt;I won't go into as much detail but if you're doing queries that don't
return many rows, the difference between drivers is negligible.&lt;/p&gt;
&lt;p&gt;See &lt;a href="https://github.com/eatonphil/databases-intuition#selects"&gt;here&lt;/a&gt; for details.&lt;/p&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;&lt;p&gt;If you are doing INSERT-heavy workloads, or you are processing large
number of rows returned from your SQL database, you might want to try
benchmarking the same workload with different drivers.&lt;/p&gt;
&lt;p&gt;And specifically, there is likely no good reason to use &lt;code&gt;lib/pq&lt;/code&gt;
anymore for accessing PostgreSQL from Go. Just use jackc/pgx.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;For INSERT-heavy workloads in Go, you may want to switch database drivers. For PostgreSQL and SQLite, the popular drivers are 20-76% slower for this workload in my tests.&lt;br /&gt;&lt;br /&gt;Some driver developers have reported issues with database/sql as an interface.&lt;a href="https://t.co/NLVp0P2uiV"&gt;https://t.co/NLVp0P2uiV&lt;/a&gt; &lt;a href="https://t.co/RxTbgMZ1MG"&gt;pic.twitter.com/RxTbgMZ1MG&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1710249941904351718?ref_src=twsrc%5Etfw"&gt;October 6, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Thu, 05 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2023-10-05-go-database-sql-overhead-on-insert-heavy-workloads.html</guid></item><item><title>Circles interactive compare</title><link>http://localhost:4000/2023/10/05/circles-interactive-compare.html</link><description>Circles interactive compare</description><author>ms75</author><pubDate>Thu, 05 Oct 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">http://localhost:4000/2023/10/05/circles-interactive-compare.html</guid></item><item><title>My Story with Snowflake</title><link>https://magis.substack.com/p/my-story-with-snowflake</link><description>A podcast about my background with Snowflake</description><author>Magis</author><pubDate>Wed, 04 Oct 2023 17:55:49 GMT</pubDate><guid isPermaLink="true">https://magis.substack.com/p/my-story-with-snowflake</guid></item><item><title>Achievement 🔓 : Mentioned in Whitepaper</title><link>http://evantravers.com/articles/2023/10/04/achievement-unlocked-mentioned-in-whitepaper/</link><description>&lt;em&gt;
  &lt;svg height="16" viewBox="0 0 24 24" width="16" xmlns="http://www.w3.org/2000/svg"&gt;
    # &lt;path d="M 4 4.44 v 2.83 c 7.03 0 12.73 5.7 12.73 12.73 h 2.83 c 0 -8.59 -6.97 -15.56 -15.56 -15.56 Z m 0 5.66 v 2.83 c 3.9 0 7.07 3.17 7.07 7.07 h 2.83 c 0 -5.47 -4.43 -9.9 -9.9 -9.9 Z M 6.18 15.64 A 2.18 2.18 0 0 1 6.18 20 A 2.18 2.18 0 0 1 6.18 15.64" fill="currentColor"&gt;&lt;/path&gt;
  &lt;/svg&gt;
  This is an RSS-only post. It's a secret! Read more about &lt;a href="/rss-club"&gt;RSS Club&lt;/a&gt;.
&lt;/em&gt;
&lt;p&gt;I love getting emails from y'all… but last month I got a unique one:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I am currently investigating the presence of humor in software. […]&lt;/p&gt;
&lt;p&gt;We noticed this post on your website where you mention using lolcommits for &lt;a href="https://evantravers.com/articles/2019/11/21/making-a-book-using-ruby/"&gt;your (rather creative!) personal project&lt;/a&gt;[…]&lt;/p&gt;
&lt;p&gt;Would you be available for meeting with us online to discuss it further, and for sharing your insight on humor in software?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I had the opportunity to chat with them… and now I'm mentioned in &lt;a href="https://drive.google.com/drive/folders/1gtBLwHIdQ3klP4TYq0PMw9djVc4R9Frd?usp=sharing"&gt;an academic whitepaper&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It's neat (and very random!) what can happen when you work in public.&lt;/p&gt;</description><author>trv.rs</author><pubDate>Wed, 04 Oct 2023 17:19:00 GMT</pubDate><guid isPermaLink="true">http://evantravers.com/articles/2023/10/04/achievement-unlocked-mentioned-in-whitepaper/</guid></item><item><title>Bomb Lab</title><link>https://web.navan.dev/posts/2023-10-04-bomb-lab.html</link><description>Walkthrough of Phases 1-6 of Bomb Lab for CSCI 2400 Computer Systems Lab 2</description><author>Navan's Archive</author><pubDate>Wed, 04 Oct 2023 16:12:00 GMT</pubDate><guid isPermaLink="true">https://web.navan.dev/posts/2023-10-04-bomb-lab.html</guid></item><item><title>Virtual machine (VM) bookmarks</title><link>https://xenodium.com/virtual-machine-vm-bookmarks</link><description>&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/abiosoft/colima"&gt;colima: Container runtimes on macOS (and Linux) with minima…&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/runfinch/finch"&gt;finch: The Finch CLI an open source client for container development&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://earthly.dev/blog/lima/"&gt;lima VM - Linux Virtual Machines On macOS - Earthly Blog&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=36668964"&gt;lima: A nice way to run Linux VMs on Mac | Hacker News&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/lima-vm/lima"&gt;lima: Linux virtual machines&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/beringresearch/macpine"&gt;macpine: Lightweight Linux VMs on MacOS&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://orbstack.dev/"&gt;OrbStack · Fast, light, simple Docker &amp;amp; Linux on macOS&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cirruslabs/tart/"&gt;tart: macOS and Linux VMs on Apple Silicon to use in CI a…&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eclecticlight.co/virtualisation-on-apple-silicon/"&gt;Virtualisation on Apple silicon – The Eclectic Light Company&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;</description><author>xenodium.com @alvaro</author><pubDate>Wed, 04 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/virtual-machine-vm-bookmarks</guid></item><item><title>Perl One-Liners Guide book announcement</title><link>https://learnbyexample.github.io/perl-oneliners-guide-announcement/</link><description>&lt;p&gt;Hello!&lt;/p&gt;
&lt;p&gt;I am pleased to announce a new version of my &lt;strong&gt;Perl One-Liners Guide&lt;/strong&gt; ebook. Examples, exercises, solutions, descriptions and external links were added/updated/corrected.&lt;/p&gt;
&lt;p&gt;When it comes to command line text processing, there are several well known tools like &lt;code&gt;grep&lt;/code&gt; for filtering, &lt;code&gt;sed&lt;/code&gt; for substitution and &lt;code&gt;awk&lt;/code&gt; for field processing. Compared to such tools, &lt;strong&gt;Perl has a feature rich regular expression engine, plenty of builtin modules and a thriving ecosystem&lt;/strong&gt;. Another advantage is that Perl is more &lt;strong&gt;portable&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This ebook will show examples for filtering and substitution features, field processing, using standard and third-party modules, multiple file processing, how to construct solutions that depend on multiple records, how to compare records and fields between two or more files, how to identify duplicates while maintaining input order and so on.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="release-offers"&gt;Release offers&lt;a class="zola-anchor" href="#release-offers"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To celebrate the new release, you can download PDF/EPUB versions of &lt;strong&gt;Perl One-Liners Guide&lt;/strong&gt; for FREE till 07-October-2023. You can still pay if you wish ;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/perl-oneliners"&gt;Gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/perl-oneliners/c/new_perl_release"&gt;Leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;All Books Bundle&lt;/strong&gt; is just $12 (normal price $32), includes all my 13 programming ebooks.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/all-books/new_perl_release"&gt;Gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/b/learnbyexample-all-books/c/new_perl_release"&gt;Leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="what-s-new"&gt;What's new?&lt;a class="zola-anchor" href="#what-s-new"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Command version updated to &lt;strong&gt;Perl 5.38.0&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;option &lt;code&gt;-g&lt;/code&gt; slurps entire file contents&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Many more exercises added&lt;/li&gt;
&lt;li&gt;Long sections split into smaller ones&lt;/li&gt;
&lt;li&gt;In general, many of the examples, exercises, solutions, descriptions and external links were updated/corrected&lt;/li&gt;
&lt;li&gt;Updated Acknowledgements section&lt;/li&gt;
&lt;li&gt;Code snippets related to info/warning sections will now appear as a single block&lt;/li&gt;
&lt;li&gt;Book title changed to &lt;strong&gt;Perl One-Liners Guide&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;New cover image&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="videos"&gt;Videos&lt;a class="zola-anchor" href="#videos"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;p&gt;On this blog, I &lt;a href="https://learnbyexample.github.io/tips/"&gt;post tips&lt;/a&gt; covering Python, command line tools and Vim. Here are video demos for these tips:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=THSMmCZQn1A&amp;amp;list=PLTv2U3HnAL4PlFDiH3FXTHXRbhWs2sB3F"&gt;Python tips&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=p0KCLusMd5Q&amp;amp;list=PLTv2U3HnAL4PNTmRqZBSUgKaiHbRL2zeY"&gt;Linux command line tips&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="testimonials"&gt;Testimonials&lt;a class="zola-anchor" href="#testimonials"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;This is fantastic! 👏 I use Perl one-liners for record and text processing a lot and this will be definitely something I will keep coming back to - I’ve already learned a trick from “Context Matching” (9) 🙂&lt;/p&gt;
&lt;p&gt;— &lt;a href="https://programming.dev/comment/3277968"&gt;feedback on Linux@lemmy.ml&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;a class="zola-anchor" href="#table-of-contents"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Preface&lt;/li&gt;
&lt;li&gt;One-liner introduction&lt;/li&gt;
&lt;li&gt;Line processing&lt;/li&gt;
&lt;li&gt;In-place file editing&lt;/li&gt;
&lt;li&gt;Field separators&lt;/li&gt;
&lt;li&gt;Record separators&lt;/li&gt;
&lt;li&gt;Using modules&lt;/li&gt;
&lt;li&gt;Multiple file input&lt;/li&gt;
&lt;li&gt;Processing multiple records&lt;/li&gt;
&lt;li&gt;Two file processing&lt;/li&gt;
&lt;li&gt;Dealing with duplicates&lt;/li&gt;
&lt;li&gt;Perl rename command&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;h2 id="web-version"&gt;Web version&lt;a class="zola-anchor" href="#web-version"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can also read the book online here: &lt;a href="https://learnbyexample.github.io/learn_perl_oneliners/"&gt;https://learnbyexample.github.io/learn_perl_oneliners/&lt;/a&gt;.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="github-repo"&gt;GitHub repo&lt;a class="zola-anchor" href="#github-repo"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Visit &lt;a href="https://github.com/learnbyexample/learn_perl_oneliners"&gt;https://github.com/learnbyexample/learn_perl_oneliners&lt;/a&gt; for markdown source, example files, exercise solutions, sample chapters and other details related to the book.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also &lt;a href="https://learnbyexample.github.io/customizing-pandoc/"&gt;my blog post&lt;/a&gt; on how to customize &lt;code&gt;pandoc&lt;/code&gt; for generating beautiful PDF/EPUB versions from GitHub style markdown.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="newsletter"&gt;Newsletter&lt;a class="zola-anchor" href="#newsletter"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Subscribe to &lt;a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly"&gt;learnbyexample weekly&lt;/a&gt; — free newsletter covering programming resources, updates on what I am creating, tips, tools, free ebooks and more, delivered every Friday.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="feedback-and-errata"&gt;Feedback and Errata&lt;a class="zola-anchor" href="#feedback-and-errata"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I would highly appreciate it if you'd &lt;strong&gt;let me know how you felt about this book&lt;/strong&gt;. It could be anything from a simple thank you, Gumroad rating, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.&lt;/p&gt;
&lt;p&gt;You can reach me via:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Issue Manager: &lt;a href="https://github.com/learnbyexample/learn_perl_oneliners/issues"&gt;https://github.com/learnbyexample/learn_perl_oneliners/issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;E-mail: &lt;code&gt;echo 'bGVhcm5ieWV4YW1wbGUubmV0QGdtYWlsLmNvbQo=' | base64 --decode&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/learn_byexample"&gt;https://twitter.com/learn_byexample&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Wed, 04 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/perl-oneliners-guide-announcement/</guid></item><item><title>My Obsidian System: PANoRAMA</title><link>https://tomodachi94.github.io/blog/panorama-obsidian/</link><description>&lt;p&gt;&lt;strong&gt;Note 4 August 2025: I have since abandoned this system. I may or may not write another piece once I have solidified a new system. I would also like to expand on the &amp;ldquo;Please take inspiration, but don&amp;rsquo;t use this&amp;rdquo; section at a later date.&lt;/strong&gt; I am leaving this up mostly for posterity.**&lt;/p&gt;
&lt;p&gt;Hello! This is an abstract walkthrough of my PKM system that I use with Obsidian and Zotero.
This is largely a mash-up of different systems, designed to fit &lt;em&gt;my&lt;/em&gt; needs.&lt;/p&gt;</description><author>Tomodachi94's Musings</author><pubDate>Wed, 04 Oct 2023 00:18:14 GMT</pubDate><guid isPermaLink="true">https://tomodachi94.github.io/blog/panorama-obsidian/</guid></item><item><title>Evolution of value</title><link>http://blog.onepatchdown.net/philosophy/2023/10/03/evolution-of-value/</link><description>&lt;h3 id="the-evolution-of-value-from-land-labor-and-capital-to-trust-time-and-perspective"&gt;The Evolution of Value: From Land, Labor, and Capital to Trust, Time, and Perspective&lt;/h3&gt;

&lt;h4 id="introduction"&gt;Introduction&lt;/h4&gt;

&lt;p&gt;The world has undergone significant transformations in the way value is created, measured, and exchanged. Traditionally, the three pillars of economic value were land, labor, and capital. However, in the modern era, these have been supplanted by less tangible but equally vital resources: trust, time, and perspective (POV).&lt;/p&gt;

&lt;h4 id="the-traditional-triad-land-labor-and-capital"&gt;The Traditional Triad: Land, Labor, and Capital&lt;/h4&gt;

&lt;h5 id="land"&gt;Land&lt;/h5&gt;
&lt;p&gt;In agrarian societies, land was the primary source of wealth. It provided the raw materials for food, shelter, and trade. Control over land often equated to power and economic stability.&lt;/p&gt;

&lt;h5 id="labor"&gt;Labor&lt;/h5&gt;
&lt;p&gt;Labor transformed raw materials into goods. The skill and efficiency of labor determined the quality and quantity of output, affecting the overall economic health of a society.&lt;/p&gt;

&lt;h5 id="capital"&gt;Capital&lt;/h5&gt;
&lt;p&gt;Capital, both financial and physical, acted as the catalyst for growth. It enabled investment in technology and infrastructure, facilitating the expansion of both land and labor productivity.&lt;/p&gt;

&lt;h4 id="the-modern-triad-trust-time-and-perspective"&gt;The Modern Triad: Trust, Time, and Perspective&lt;/h4&gt;

&lt;h5 id="trust"&gt;Trust&lt;/h5&gt;
&lt;p&gt;In today’s interconnected world, trust has become a cornerstone of value. Whether it’s trust in a brand, a technology, or a system, it enables transactions and relationships that form the basis of modern economies.&lt;/p&gt;

&lt;h5 id="time"&gt;Time&lt;/h5&gt;
&lt;p&gt;Time has always been a finite resource, but its importance has escalated in the age of information. Efficiency, speed, and the ability to manage time effectively are now critical factors in economic success.&lt;/p&gt;

&lt;h5 id="perspective"&gt;Perspective&lt;/h5&gt;
&lt;p&gt;In a world saturated with information and options, perspective or POV has become a valuable asset. Unique insights, innovative thinking, and the ability to solve complex problems are highly sought after.&lt;/p&gt;

&lt;h4 id="the-shift-explained"&gt;The Shift Explained&lt;/h4&gt;

&lt;h5 id="technological-advancements"&gt;Technological Advancements&lt;/h5&gt;
&lt;p&gt;The digital revolution has minimized the importance of land and physical capital. Virtual real estate and digital assets have become new forms of land and capital.&lt;/p&gt;

&lt;h5 id="globalization"&gt;Globalization&lt;/h5&gt;
&lt;p&gt;The global market has made labor more of a commodity, easily outsourced or automated. Trust, however, cannot be outsourced; it must be built and maintained.&lt;/p&gt;

&lt;h5 id="information-age"&gt;Information Age&lt;/h5&gt;
&lt;p&gt;The explosion of information has made time and perspective more valuable. The ability to quickly assimilate information and offer a unique POV is invaluable.&lt;/p&gt;

&lt;h4 id="conclusion"&gt;Conclusion&lt;/h4&gt;

&lt;p&gt;The transition from land, labor, and capital to trust, time, and perspective reflects the evolving complexities of the modern world. While the traditional triad laid the foundation for economic systems, the modern triad is shaping the future, emphasizing the intangible yet indispensable resources that drive today’s economies.&lt;/p&gt;</description><author>Blogity blog blog. Journal</author><pubDate>Wed, 04 Oct 2023 00:09:00 GMT</pubDate><guid isPermaLink="true">http://blog.onepatchdown.net/philosophy/2023/10/03/evolution-of-value/</guid></item><item><title>The 10_001 magic number</title><link>https://kinduff.com/2023/10/03/the-10-001-magic-number/</link><description>Unraveling the story behind a magic number in code</description><author>Alejandro AR (kinduff)</author><pubDate>Tue, 03 Oct 2023 14:43:00 GMT</pubDate><guid isPermaLink="true">https://kinduff.com/2023/10/03/the-10-001-magic-number/</guid></item><item><title>Four Pillars of Disagreement</title><link>http://blog.onepatchdown.net/philosophy/2023/10/03/four-pillars-disagreement/</link><description>&lt;h2 id="the-four-pillars-of-disagreement-non-negotiable-categories"&gt;The Four Pillars of Disagreement: Non-Negotiable Categories&lt;/h2&gt;

&lt;p&gt;Disagreements aren’t just random occurrences; they are structured, predictable, and most importantly, classifiable. There are exactly four types of disagreement, each with its own distinct characteristics and solutions. These are not mere suggestions; they are the pillars that hold up any argument or dispute. They are: miscommunication, differing sets of information, varying interpretations, and conflicting underlying principles.&lt;/p&gt;

&lt;h3 id="miscommunication-the-unquestionable-culprit"&gt;Miscommunication: The Unquestionable Culprit&lt;/h3&gt;
&lt;p&gt;Let’s get this straight: when two colleagues can’t even agree on a meeting time—one thinking it’s at 3 PM and the other at 4 PM—that’s not just an oversight. It’s a glaring example of miscommunication, pure and simple. The solution is equally straightforward: clarify, confirm, and move on.&lt;/p&gt;

&lt;h3 id="differing-sets-of-information-the-undeniable-divider"&gt;Differing Sets of Information: The Undeniable Divider&lt;/h3&gt;
&lt;p&gt;Consider two employees locked in debate over a new company policy. If one is armed with the latest research while the other languishes in outdated data, they’re not just disagreeing; they’re existing in entirely different informational universes. The fix? Update, align, and proceed. Anything less is a disservice to the discussion.&lt;/p&gt;

&lt;h3 id="varying-interpretations-the-inarguable-confounder"&gt;Varying Interpretations: The Inarguable Confounder&lt;/h3&gt;
&lt;p&gt;Text messages are not just words on a screen; they are a battleground for interpretation. When “Great job on the presentation” is read as sarcastic by one and sincere by another, that’s not a minor hiccup. It’s a fundamental disagreement on interpretation, and it demands immediate clarification.&lt;/p&gt;

&lt;h3 id="conflicting-underlying-principles-the-irrefutable-core"&gt;Conflicting Underlying Principles: The Irrefutable Core&lt;/h3&gt;
&lt;p&gt;When two friends argue politics from opposing ideological platforms—one valuing individual freedom and the other social equality—there’s no skirting the issue. This is a clash of principles, as foundational as it gets. Don’t expect a quick fix; this type of disagreement calls for deep reflection and, often, an acceptance of ideological diversity.&lt;/p&gt;

&lt;h3 id="conclusion-the-immutable-truth"&gt;Conclusion: The Immutable Truth&lt;/h3&gt;
&lt;p&gt;These are not just types of disagreements; they are the framework within which all disagreements occur. Whether it’s the undeniable role of miscommunication, the clear-cut impact of differing information, the indisputable effect of varying interpretations, or the unyielding influence of conflicting principles, these four categories are the bedrock of all disputes. Recognize them, address them, and only then can you navigate the complex landscape of human disagreement. The goal is not to win, but to understand.&lt;/p&gt;</description><author>Blogity blog blog. Journal</author><pubDate>Tue, 03 Oct 2023 04:00:00 GMT</pubDate><guid isPermaLink="true">http://blog.onepatchdown.net/philosophy/2023/10/03/four-pillars-disagreement/</guid></item><item><title>CV/Resume</title><link>https://muscaw.dev/page/cv/</link><description>&lt;h1 id="cvresume"&gt;CV/Resume&lt;/h1&gt;

&lt;p&gt;This browser does not support inline PDFs. Please download the PDF to view it: &lt;a href="https://muscaw.dev/page/cv/cv.pdf"&gt;Download PDF&lt;/a&gt;&lt;/p&gt;</description><author>Muscaw</author><pubDate>Tue, 03 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://muscaw.dev/page/cv/</guid></item><item><title>much worse than kids stomping that pig's bladder</title><link>https://taylor.town/pigs-bladder</link><description>&lt;p&gt;&lt;img alt="_Boys Blowing Bladder_ by Peter Perez Burdett" src="https://taylor.town/906px-BoysBlowingBladder_by_Peter_Perez_Burdett.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://books.google.com/books?id=1-_eDwAAQBAJ&amp;amp;pg=PA11&amp;amp;lpg=PA11&amp;amp;dq=%22pa+said.+he+was+blowing+up+the%22+little+house&amp;amp;source=bl&amp;amp;ots=04SWzzNgyW&amp;amp;sig=ACfU3U2x5thzHQd-SM-1bAQZMOfnDT4J7w&amp;amp;hl=en&amp;amp;sa=X&amp;amp;ved=2ahUKEwjo4sSE6NmBAxUtJEQIHXOCDzAQ6AF6BAgJEAM#v=onepage&amp;amp;q=%22pa%20said.%20he%20was%20blowing%20up%20the%22%20little%20house&amp;amp;f=false"&gt;&lt;img alt="_Little House in the Big Woods_ by Laura Ingalls Wilder" src="https://taylor.town/little-house-bladder.png" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;small class="footnote" id="footnote-note"&gt;Extra credit: &lt;a href="http://www.shiro1000.jp/pneu/pneu2-e.html"&gt;a study of balloon&lt;/a&gt;,
&lt;a href="http://movingtotheprairie.blogspot.com/2011/03/little-house-in-big-woods-chapter-1.html"&gt;Little House on the Prairie: 21st Century Edition&lt;/a&gt;,
&lt;a href="https://www.motherearthnews.com/diy/make-a-pig-bladder-ball-zmaz83sozshe/"&gt;Make a Pig-Bladder Ball&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;After emptying its residual urine, an
&lt;a href="https://en.wikipedia.org/wiki/Pig_bladder"&gt;inflated pig's bladder&lt;/a&gt; can be
kicked, tossed,
&lt;a href="https://www.appalachianhistory.net/2017/12/take-it-outside-christmas-morning-and.html"&gt;stomped&lt;/a&gt;,
and fashioned into a
&lt;a href="https://www.efdss.org/learning/resources/glossary/9728-fools-stick#:~:text=Carried%20by%20the%20fool%2C%20the,dancers%20and%20occasionally%20the%20audience."&gt;fool's stick&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Yes, children played with animal organs.&lt;/p&gt;
&lt;p&gt;Balloon animals
&lt;a href="https://zephyrsolutions.com/original-balloon-animals-made-animals/"&gt;were made from intestines&lt;/a&gt;,
inflated and tied into shapes of animals (intestinception).&lt;/p&gt;
&lt;p&gt;If I saw my neighbor's daughter playing with intestines, I'd call the cops.&lt;/p&gt;
&lt;p&gt;But if only those happy children of yore could see me lavishing on their dying
planet.&lt;/p&gt;
&lt;p&gt;I violated &lt;a href="https://wiki.c2.com/?BoyScoutRule"&gt;the scout's rule&lt;/a&gt;: I left my
campsite worse than how I found it.&lt;/p&gt;
&lt;p&gt;Forget carbon and ozone and plastic and coal and whatever -- every year, what I
steal from the commons outweighs my contributions.&lt;/p&gt;
&lt;p&gt;A pig's bladder can be grown and regrown, but plastic sits in the ocean
forever(ish).&lt;/p&gt;
&lt;p&gt;A pig's bladder can make a game of football, or be reconstituted
&lt;a href="https://www.vox.com/future-perfect/23817808/pig-farm-investigation-feedback-immunity-feces-intestines"&gt;into cheap calories for other pigs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;My food cannot be made like this indefinitely. My life cannot be powered like
this indefinitely. My toys and electronics and furniture and trinkets cannot be
made like this indefinitely.&lt;/p&gt;
&lt;p&gt;I want to build life from the things around me.&lt;/p&gt;</description><author>taylor.town</author><pubDate>Tue, 03 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/pigs-bladder</guid></item><item><title>A Tale of Two Pharmacies</title><link>http://pxtl.ca/2023/10/03/a-tale-of-two-pharmacies/</link><description>&lt;p&gt;What follows is the story of why I no longer go to my old pharmacy.  This is
recounted from memory, and some of the details may be inaccurate, but the gist
is there.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;drops in at local pharmacy&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;"I'm running out of my meds, can you refill them?"&lt;/p&gt;

&lt;p&gt;"Sorry, you have to come closer to refill-day, so come back Saturday."&lt;/p&gt;

&lt;p&gt;"Okay, I'll come back then."&lt;/p&gt;

&lt;p&gt;...&lt;/p&gt;

&lt;p&gt;"Okay,  it's Saturday, here to refill my meds."&lt;/p&gt;

&lt;p&gt;"... yeah, you're out of refills.  We'll have to talk to your doctor."&lt;/p&gt;

&lt;p&gt;"... but it's the weekend?"&lt;/p&gt;

&lt;p&gt;"Oh, we'll sort it out on Monday."&lt;/p&gt;

&lt;p&gt;...&lt;/p&gt;</description><author>Pxtl.ca</author><pubDate>Tue, 03 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">http://pxtl.ca/2023/10/03/a-tale-of-two-pharmacies/</guid></item><item><title>Circles interactive different</title><link>http://localhost:4000/2023/10/03/circles-interactive-different.html</link><description>Circles interactive different</description><author>ms75</author><pubDate>Tue, 03 Oct 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">http://localhost:4000/2023/10/03/circles-interactive-different.html</guid></item><item><title>How I teach Ansible to my colleagues: A hands-on training session.</title><link>https://www.zufallsheld.de/2023/10/02/how-i-teach-ansible/</link><description>&lt;p&gt;As someone with years of experience using and teaching Ansible I want to share with you how I teach it to my colleagues in the most practical way&amp;nbsp;possible.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;

&lt;p&gt;However, before I get into the actual content of the training, it should be emphasized how important a functioning and identically …&lt;/p&gt;</description><author>zufallsheld</author><pubDate>Mon, 02 Oct 2023 23:00:00 GMT</pubDate><guid isPermaLink="true">https://www.zufallsheld.de/2023/10/02/how-i-teach-ansible/</guid></item><item><title>TIL how to create Azure Prometheus datasources with Ansible</title><link>https://www.zufallsheld.de/2023/10/02/til-how-to-create-grafana-azure-prometheus-datasources/</link><description>&lt;!-- PELICAN_NO_JINJA --&gt;

&lt;p&gt;Since I spent some time today on this, I&amp;#8217;d rather write it down.
Creating a Prometheus datasource that uses Azure Authentication was not straight&amp;nbsp;forward.&lt;/p&gt;
&lt;p&gt;Here&amp;#8217;s the end&amp;nbsp;result:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
- name: Create a datasource in Grafana
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Create prometheus datasource
      community.grafana.grafana_datasource:
        name …&lt;/code&gt;&lt;/pre&gt;</description><author>zufallsheld</author><pubDate>Mon, 02 Oct 2023 23:00:00 GMT</pubDate><guid isPermaLink="true">https://www.zufallsheld.de/2023/10/02/til-how-to-create-grafana-azure-prometheus-datasources/</guid></item><item><title>Summary: Deep &amp;amp; Cross Net v2</title><link>https://blog.lukesalamone.com/posts/deep-cross-net-v2/</link><description>&lt;p&gt;Paper link: &lt;a href="https://arxiv.org/pdf/2008.13535"&gt;https://arxiv.org/pdf/2008.13535&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Learning to rank is an important problem in many machine-learning products such as search, recommendation, and advertising. Originally, many machine learning systems used simple logistic regression models, but it quickly became apparent that combining two or more features together was &lt;a href="https://www.ismll.uni-hildesheim.de/pub/pdfs/Rendle2010FM.pdf"&gt;even better&lt;/a&gt;. This is called feature crossing.&lt;/p&gt;
&lt;p&gt;A lot of research and engineering work has gone into learning useful feature crosses. The fundamental problem is that although higher-order feature crosses can be more informative, they are also more sparse, and the number of high order features grows exponentially. Some attempts to address this have been:&lt;/p&gt;</description><author>Luke Salamone's Blog</author><pubDate>Mon, 02 Oct 2023 22:39:18 GMT</pubDate><guid isPermaLink="true">https://blog.lukesalamone.com/posts/deep-cross-net-v2/</guid></item><item><title>AWAsary v1.3.0</title><link>https://tig.pt/awasary-v1-3-0/</link><description>&lt;figure class="kg-card kg-bookmark-card"&gt;&lt;a class="kg-bookmark-container" href="https://apps.apple.com/gh/app/awsary-aws-dictionary/id1634871091?ref=tig.pt"&gt;&lt;div class="kg-bookmark-content"&gt;&lt;div class="kg-bookmark-title"&gt;&amp;#x200e;AWSary - AWS Dictionary&lt;/div&gt;&lt;small&gt;&lt;div class="kg-bookmark-description"&gt;&amp;#x200e;AWSary is the ultimate resource for AWS Cloud Consultants, students, and anyone looking to stay up-to-date with AWS services. With this app, you&amp;#x2019;ll have access to a comprehensive AWS dictionary, allowing you to easily search and learn about various AWS services.&lt;/div&gt;&lt;/small&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;</description><author>Tiago Rodrigues</author><pubDate>Mon, 02 Oct 2023 21:37:00 GMT</pubDate><guid isPermaLink="true">https://tig.pt/awasary-v1-3-0/</guid></item><item><title>A Weapon to Surpass Metal Gear</title><link>https://lagomor.ph/indieweb/20231002/1149-bookmark/</link><description/><author>Home on Lagomorph</author><pubDate>Mon, 02 Oct 2023 20:50:00 GMT</pubDate><guid isPermaLink="true">https://lagomor.ph/indieweb/20231002/1149-bookmark/</guid></item><item><title>Note 115</title><link>https://qubyte.codes/notes/1696245476866</link><description>&lt;p&gt;Jet lag has its perks! Up before sunrise for a coffee at a nice little place called Fahrenheit in downtown Toronto.&lt;/p&gt;

    &lt;img alt="A photograph of a cup of black coffee on a wooden bar." src="https://qubyte.codes/images/1696245377641.jpeg" /&gt;</description><author>Qubyte Codes</author><pubDate>Mon, 02 Oct 2023 14:17:56 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/notes/1696245476866</guid></item><item><title>Color-Encoded Information Protocol</title><link>https://mbutler.org/color-encoded-information-protocol/</link><description>Version 1.1 Table of Contents Introduction Objectives Rationale Color Term Choices Ordering of Color Index Use of ASCII Characters for Keys Use of Numbers for Values Key/Value Pair Representation Color to Number Mapping Protocol Description Encoding Decoding Special Commands and Message Buffering Use Cases Future Work and Recommendations 1. Introduction The Color-Encoded Information Protocol (CEIP) [&amp;#8230;]</description><author>mbutler</author><pubDate>Mon, 02 Oct 2023 05:12:10 GMT</pubDate><guid isPermaLink="true">https://mbutler.org/color-encoded-information-protocol/</guid></item><item><title>Living dead internet theory</title><link>https://seirdy.one/notes/2023/10/01/living-dead-internet-theory/</link><description>&lt;p&gt;I propose an alternative to the &lt;a href="https://en.wikipedia.org/wiki/Dead_Internet_theory"&gt;Dead Internet Theory&lt;/a&gt; called the Living Dead Internet Theory, an exaggerated version of my actual beliefs:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Any content written to optimize against algorithmic curation is as bad as algorithmically generated content.&lt;/li&gt;&lt;li&gt;Algorithmically-curated content is disproportionally biased towards content optimized for said algorithm.&lt;/li&gt;&lt;li&gt;The most accessible content might as well be written by bots.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Don’t use short-tail search queries for discovery.&lt;/p&gt;</description><author>All content on Seirdy’s Home</author><pubDate>Mon, 02 Oct 2023 04:41:41 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/notes/2023/10/01/living-dead-internet-theory/</guid></item><item><title>Notes on A hacker's guide to Language Models</title><link>https://vit.baisa.cz/notes/learn/ml/hackers-guide-to-llm/</link><description>notes taken during the video by Jeremy Howard</description><author>Vít Baisa</author><pubDate>Mon, 02 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://vit.baisa.cz/notes/learn/ml/hackers-guide-to-llm/</guid></item><item><title>What would a web app canary look like?</title><link>https://ntietz.com/blog/web-app-canaries/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;Recently, I listened to an &lt;a href="https://changelog.com/podcast/557"&gt;interview with Haroon Meer&lt;/a&gt;, the founder of a company focused on honeypots.
Honeypots (also known as canaries or tripwires) are used to detect network intrusions and people nosing around at things they're not supposed to.
They are an essential component of modern network security.&lt;/p&gt;
&lt;p&gt;It got me thinking: These are part of network security, so could we use this same concept for application security?
What would it look like to setup a honeypot in a web app?
How much can I make our pentesters personally &lt;em&gt;loathe me&lt;/em&gt;?&lt;/p&gt;
&lt;h1 id="the-idea-behind-honeypots"&gt;The idea behind honeypots&lt;/h1&gt;
&lt;p&gt;The main concept behind a honeypot is that you deploy something that looks like an attractive target and monitor it for attempts to access it.
Employees and legitimate users know where they're supposed to go, but attackers have to do some discovery, so they're likely to stumble across it.
When access to the honeypot is detected, you can then respond however you'd like.
Typically you will alert people to the honeypot access.&lt;/p&gt;
&lt;p&gt;We could do this within our web applications, too.
A component of penetration tests can include attempts to escalate privileges, or to access data you are not supposed to have access to.
There are least a few diferent sorts of honeypots we could use.&lt;/p&gt;
&lt;p&gt;But first, we need to think about what we want to protect against.
Here, I'm going to consider two classes of bad actors.
The first is &lt;em&gt;malicious users&lt;/em&gt;, who try to use their legitimate access as a user of the application to gain access to information or resources they're not supposed to have access to.
The second is &lt;em&gt;insider threats&lt;/em&gt;, people who have high levels of privilege due to their role working on the application.
Honeypots can be useful for both of these scenarios, but they have different considerations.&lt;/p&gt;
&lt;h1 id="sweetening-the-web-app"&gt;Sweetening the web app&lt;/h1&gt;
&lt;p&gt;The concept of a honeypot is nice and simple.
What does it look like in a web app?
There are a few obvious ideas that are also pretty easy to implement.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tripwires on well-known values.&lt;/strong&gt;
There are some values which you know may be tried if someone is just nosing around.
If you use integers for IDs, you could put a tripwire on 0 and powers of 2 (while ensuring these aren't used by the application).
This would let you detect enumeration attacks: since these values wouldn't be legitimately used, attempts to access them are a sign someone is being naughty.
&lt;em&gt;Protects against malicious users; doesn't help with insider threats.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Decoy records in the admin interface.&lt;/strong&gt;
You can make fake records in your database which are real in one sense (they exist in the DB) but fake in that they are not for legitimate users.
Then, you can monitor for access to these through various internal facing tools.
If someone accesses these, that means that they're accessing records they have no legitimate business purpose to access (probably).
&lt;em&gt;Protects some against insider threats, and malicious users who escalated privilege.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Extraneous IDs embedded in pages and responses.&lt;/strong&gt;
There's no law that says that everything you return in a response has to be legitimate.
You can populate extra fields with fake data if you know that the client using it is not going to do anything with that data.
(This only really works if you control the client, otherwise you're setting up your users for failure.)
If you receive requests with this fake data (decoy IDs or decoy endpoints) then you'll know someone was poking around for access to more things.
This could be an attack, but it could also just be a curious dev who's using your product.
&lt;em&gt;Protects against malicious users, punishes curious users.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tripwires on common URL paths.&lt;/strong&gt;
There are some common paths which many web apps use, like &lt;code&gt;/admin&lt;/code&gt; or &lt;code&gt;/wp-admin&lt;/code&gt;.
If your application does &lt;em&gt;not&lt;/em&gt; use these, you can place a honeypot on that URL.
Then, if you get requests on that URL, you'll know that someone is nosing around a little.
This is likely to be noisy; since these are common, you'll get a lot of random traffic on them hoping you've got an outdated WordPress installation.
But, it can provide valuable signal, and if you get a request on this from a &lt;em&gt;logged in&lt;/em&gt; user... yikes.
&lt;em&gt;Protects against malicious users, annoys whoever gets paged.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The possibilities here are almost endless!
Some are good ideas, some are bad.
But what's clear is that it's doable.&lt;/p&gt;
&lt;p&gt;If you can think of any other ideas that will make a pentester's life absolutely miserable when testing a web app, please let me know!&lt;/p&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 02 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/web-app-canaries/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Hey, Computer, Make Me a Font</title><link>https://serce.me/posts/2023-10-02-hey-computer-make-me-a-font</link><description>This is a story of my journey learning to build generative ML models from scratch and teaching a computer to create fonts in the process.</description><author>SerCe's blog</author><pubDate>Mon, 02 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://serce.me/posts/2023-10-02-hey-computer-make-me-a-font</guid></item><item><title>Week 1 Making Embedded Systems</title><link>https://dannas.name/2023/10/02/making-embedded-systems-week1</link><description>Week 1 of 10 of the Making Embedded Systems course.</description><author>Daniel Näslund</author><pubDate>Mon, 02 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://dannas.name/2023/10/02/making-embedded-systems-week1</guid></item><item><title>Dotenv is dead</title><link>https://francoisbest.com/posts/2023/dotenv-is-dead?utm_source=rss</link><description>Long live type-safe environment variable management in Node.js</description><author>Articles by François Best</author><pubDate>Mon, 02 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://francoisbest.com/posts/2023/dotenv-is-dead?utm_source=rss</guid></item><item><title>Deriving Recursion from First Principles</title><link>https://lambdaland.org/posts/2023-10-03_recursion_from_first_principles/</link><description>&lt;div class="info"&gt;
&lt;p&gt;These are some of my class notes. Learning to derive the &lt;a class="link-external" href="https://en.wikipedia.org/wiki/Fixed-point_combinator#Y_combinator" rel="external"&gt;Y Combinator&lt;/a&gt; from first principles is something I’ve always wanted to do. This isn’t &lt;em&gt;quite&lt;/em&gt; the Y Combinator, but it’s very close and it still gets you recursion without relying on recursive structures to begin with.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;In the beginning, we write a recursive function to compute the length of a list:&lt;/p&gt;
&lt;pre class="giallo" style="color: #D8DEE9; background-color: #2E3440;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;let*&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; ([&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;λ&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;lst&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;             (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;if&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;null?&lt;/span&gt;&lt;span&gt; lst&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #B48EAD;"&gt;                 0&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;                 (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;+&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;cdr&lt;/span&gt;&lt;span&gt; lst&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)))))])&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;  (&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; '&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt;1 2 3&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a class="link-external" href="https://docs.racket-lang.org/reference/let.html" rel="external"&gt;&lt;code&gt;let*&lt;/code&gt;&lt;/a&gt; syntax allows us to create local variable bindings that can reference themselves. But let’s suppose we don’t have &lt;code&gt;let*&lt;/code&gt;—what do we do?&lt;/p&gt;
&lt;p&gt;We can make a function that we give to itself. That then returns the function we want, with the outer function in scope. So, the outer function &lt;code&gt;len&lt;/code&gt; in this example has “type”[^fn:1] &lt;code&gt;$self → Int → Int&lt;/code&gt;. That makes it clear that to get the &lt;code&gt;Int → Int&lt;/code&gt; function we want, we have to pass the function to itself.&lt;/p&gt;
&lt;pre class="giallo" style="color: #D8DEE9; background-color: #2E3440;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;let&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; ([&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;λ&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;             (&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;λ&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;lst&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;               (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;if&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;null?&lt;/span&gt;&lt;span&gt; lst&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #B48EAD;"&gt;                   0&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;                   (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;+&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; ((&lt;/span&gt;&lt;span&gt;len len&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;) (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;cdr&lt;/span&gt;&lt;span&gt; lst&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;))))))])&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;  ((&lt;/span&gt;&lt;span&gt;len len&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; '&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt;1 2 3&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But it’d be nice if we could pull out that &lt;code&gt;(len len)&lt;/code&gt; in the body of the function. Let’s call that &lt;code&gt;len1&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="giallo" style="color: #D8DEE9; background-color: #2E3440;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;let&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; ([&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;λ&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;             (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;let&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; ([&lt;/span&gt;&lt;span&gt;len1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;λ&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;l&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;) ((&lt;/span&gt;&lt;span&gt;len len&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;span&gt; l&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;))])&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;               (&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;λ&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;lst&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;                 (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;if&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;null?&lt;/span&gt;&lt;span&gt; lst&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #B48EAD;"&gt;                     0&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;                     (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;+&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;len1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;cdr&lt;/span&gt;&lt;span&gt; lst&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)))))&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;               ))])&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;  ((&lt;/span&gt;&lt;span&gt;len len&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; '&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt;1 2 3&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that what we &lt;em&gt;can’t&lt;/em&gt; do is this:&lt;/p&gt;
&lt;pre class="giallo" style="color: #D8DEE9; background-color: #2E3440;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;let&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; ([&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;λ&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;             (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;let&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; ([&lt;/span&gt;&lt;span&gt;len1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;len len&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)])&lt;/span&gt;&lt;span style="color: #616E88;"&gt;    ; problem here!&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;               (&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;λ&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;lst&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;                 (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;if&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;null?&lt;/span&gt;&lt;span&gt; lst&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #B48EAD;"&gt;                     0&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;                     (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;+&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;len1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;cdr&lt;/span&gt;&lt;span&gt; lst&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)))))&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;               ))])&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;  ((&lt;/span&gt;&lt;span&gt;len len&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; '&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt;1 2 3&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because this is an &lt;em&gt;eager&lt;/em&gt; language, so that’d loop forever.&lt;/p&gt;
&lt;p&gt;Let’s do that same trick for the outer &lt;code&gt;len len&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="giallo" style="color: #D8DEE9; background-color: #2E3440;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;let&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; ([&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;λ&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;             (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;let&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; ([&lt;/span&gt;&lt;span&gt;len1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;λ&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;l&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;) ((&lt;/span&gt;&lt;span&gt;len len&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;span&gt; l&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;))])&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;               (&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;λ&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;lst&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;                 (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;if&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;null?&lt;/span&gt;&lt;span&gt; lst&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #B48EAD;"&gt;                     0&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;                     (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;+&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;len1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;cdr&lt;/span&gt;&lt;span&gt; lst&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)))))&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;               ))])&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;  (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;let&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; ([&lt;/span&gt;&lt;span&gt;len1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;len len&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)])&lt;/span&gt;&lt;span style="color: #616E88;"&gt;               ; type of len1 is Int → Int&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;    (&lt;/span&gt;&lt;span&gt;len1&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; '&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt;1 2 3&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;))))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that the &lt;code&gt;(len len)&lt;/code&gt; at the end of that is OK because we’re not doing another self application.&lt;/p&gt;
&lt;p&gt;Let’s change that inner &lt;code&gt;let&lt;/code&gt; to a &lt;code&gt;λ&lt;/code&gt; just for fun.&lt;/p&gt;
&lt;pre class="giallo" style="color: #D8DEE9; background-color: #2E3440;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;let&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; ([&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;λ&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;             ((&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;λ&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;len1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;                (&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;λ&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;lst&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;                  (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;if&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;null?&lt;/span&gt;&lt;span&gt; lst&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 0&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;+&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;len1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;cdr&lt;/span&gt;&lt;span&gt; lst&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;))))))&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;              (&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;λ&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;l&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;) ((&lt;/span&gt;&lt;span&gt;len len&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;span&gt; l&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;))))])&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;  ((&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;λ&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;len1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;     (&lt;/span&gt;&lt;span&gt;len1&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; '&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt;1 2 3&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)))&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;   (&lt;/span&gt;&lt;span&gt;len len&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rename &lt;code&gt;len&lt;/code&gt; → &lt;code&gt;self&lt;/code&gt;, &lt;code&gt;len1&lt;/code&gt; → &lt;code&gt;rec&lt;/code&gt;, and just return the function without calling it:&lt;/p&gt;
&lt;pre class="giallo" style="color: #D8DEE9; background-color: #2E3440;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;let&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; ([&lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;λ&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;              ((&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;λ&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;rec&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;                 (&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;λ&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;lst&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;                   (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;if&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;null?&lt;/span&gt;&lt;span&gt; lst&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 0&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;+&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;rec&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;cdr&lt;/span&gt;&lt;span&gt; lst&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;))))))&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;               (&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;λ&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;) ((&lt;/span&gt;&lt;span&gt;self self&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;span&gt; x&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;))))])&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;  (&lt;/span&gt;&lt;span&gt;self self&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ok, we are ready… let’s go ahead an extract that part &lt;code&gt;(λ (lst) ...)&lt;/code&gt;: that’s the only part of this that knows anything about computing the length of lists. We’ll wrap the whole thing in a function called &lt;code&gt;mk_rec&lt;/code&gt; which takes a function of two arguments: the first argument we will pass &lt;code&gt;rec&lt;/code&gt; to, and the second is the actual argument to the function.&lt;/p&gt;
&lt;pre class="giallo" style="color: #D8DEE9; background-color: #2E3440;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;define&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;mk_rec&lt;/span&gt;&lt;span&gt; fn_x&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;span style="color: #616E88;"&gt; ; takes two args: the recursive thingy and the argument&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;  (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;let&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; ([&lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;λ&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;                ((&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;λ&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;rec&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;                   (&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;λ&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;) (&lt;/span&gt;&lt;span&gt;fn_x rec x&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)))&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;                 (&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;λ&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;) ((&lt;/span&gt;&lt;span&gt;self self&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;span&gt; x&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;))))])&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;    (&lt;/span&gt;&lt;span&gt;self self&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)))&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;define&lt;/span&gt;&lt;span&gt; len&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;mk_rec&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;λ&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;rec lst&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;) (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;if&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;null?&lt;/span&gt;&lt;span&gt; lst&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 0&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;+&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;rec&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;cdr&lt;/span&gt;&lt;span&gt; lst&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)))))))&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt; '&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt;1 2 3&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;))&lt;/span&gt;&lt;span style="color: #616E88;"&gt;                          ; returns 3&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;define&lt;/span&gt;&lt;span&gt; fact&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;mk_rec&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #81A1C1;"&gt;λ&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;rec x&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;) (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;if&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;zero?&lt;/span&gt;&lt;span&gt; x&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;*&lt;/span&gt;&lt;span&gt; x&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span&gt;rec&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt; (&lt;/span&gt;&lt;span style="color: #88C0D0;"&gt;-&lt;/span&gt;&lt;span&gt; x&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 1&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)))))))&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #ECEFF4;"&gt;(&lt;/span&gt;&lt;span&gt;fact&lt;/span&gt;&lt;span style="color: #B48EAD;"&gt; 5&lt;/span&gt;&lt;span style="color: #ECEFF4;"&gt;)&lt;/span&gt;&lt;span style="color: #616E88;"&gt;                                ; returns 120&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that that isn’t actually the the Y combinator exactly. But hey we have recursion without using recursion!&lt;/p&gt;
&lt;p&gt;+++
&lt;em&gt;Source = “FreeBSD &lt;code&gt;fortune&lt;/code&gt; files; this is probably my favorite quine ever.&lt;/em&gt;”&lt;/p&gt;</description><author>Lambda Land</author><pubDate>Mon, 02 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://lambdaland.org/posts/2023-10-03_recursion_from_first_principles/</guid></item><item><title>Note 114</title><link>https://qubyte.codes/notes/1696201219546</link><description>&lt;p&gt;I’ve arrived in Toronto on business, and the hotel has pie.&lt;/p&gt;

    &lt;img alt="A slice of cream pie. It’s about 5cm deep, with a radius of about 12cm and an arc of about 45 degrees. It’s topped with shards of white chocolate, coconut, and chocolate brittle. It’s served on a white place, upon a low wooden coffee table." src="https://qubyte.codes/images/1696201061345.jpeg" /&gt;</description><author>Qubyte Codes</author><pubDate>Mon, 02 Oct 2023 02:00:19 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/notes/1696201219546</guid></item><item><title>Closing the gap on fediverse hashtag visibility with hashtag-importer</title><link>https://anisse.astier.eu/hashtag-importer-release.html</link><description>&lt;p&gt;I have released a small application, &lt;a href="https://github.com/anisse/hashtag-importer"&gt;hashtag-importer&lt;/a&gt;, that users of a Mastodon instance can use to slowly import more content from low-traffic hashtags, into their instance (with their admin's permission).&lt;/p&gt;
&lt;h1&gt;Why hashtag-importer&lt;/h1&gt;
&lt;p&gt;In the fediverse, your server might not see all posts made by everyone; it should only see posts …&lt;/p&gt;</description><author>Linux Engineer's random thoughts</author><pubDate>Mon, 02 Oct 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://anisse.astier.eu/hashtag-importer-release.html</guid></item><item><title>Book - Parable of the Sower / Parable of the Talents</title><link>https://mfashby.net/posts/2023-10-01-parable-of-the-sower/</link><description/><author>Home on mfashby.net</author><pubDate>Mon, 02 Oct 2023 00:23:00 GMT</pubDate><guid isPermaLink="true">https://mfashby.net/posts/2023-10-01-parable-of-the-sower/</guid></item><item><title>The Orwellian Hypocrisy of Modern Online Job Apps</title><link>https://synystron.substack.com/p/the-orwellian-hypocrisy-of-modern</link><description>and the creepy descent into more gov/corp Surveillance Capitalism, and de facto grift by the 2 major US political parties</description><author>Synystron Synlogica</author><pubDate>Sun, 01 Oct 2023 22:13:20 GMT</pubDate><guid isPermaLink="true">https://synystron.substack.com/p/the-orwellian-hypocrisy-of-modern</guid></item><item><title>Japan impressions</title><link>https://somethingaboutcats.substack.com/p/japan-impressions</link><description>Thoughts on zoning, hardware, and toilets</description><author>Something About Cats</author><pubDate>Sun, 01 Oct 2023 20:08:46 GMT</pubDate><guid isPermaLink="true">https://somethingaboutcats.substack.com/p/japan-impressions</guid></item><item><title>Database Migrations</title><link>https://vadimkravcenko.com/shorts/database-migrations/</link><description>&lt;p&gt;Question:&lt;/p&gt;
&lt;/p&gt;
&lt;p&gt; Answer:&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://vadimkravcenko.com/shorts/database-migrations/"&gt;Database Migrations&lt;/a&gt; appeared first on &lt;a href="https://vadimkravcenko.com"&gt;Vadim Kravcenko&lt;/a&gt;.&lt;/p&gt;</description><author>Vadim Kravcenko</author><pubDate>Sun, 01 Oct 2023 13:03:54 GMT</pubDate><guid isPermaLink="true">https://vadimkravcenko.com/shorts/database-migrations/</guid></item><item><title>The Pooch Pontoon</title><link>https://solomon.io/the-pooch-pontoon/</link><description>For Holly’s birthday this year we chartered a Pooch Pontoon on Lake Lanier for an afternoon.</description><author>Sam Solomon</author><pubDate>Sun, 01 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://solomon.io/the-pooch-pontoon/</guid></item><item><title>I Moved to San Francisco</title><link>https://olivergilan.com/i-moved-to-sf/</link><description>It's time for a new era in my life. I'm now in the arena.</description><author>Oliver Gilan</author><pubDate>Sun, 01 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://olivergilan.com/i-moved-to-sf/</guid></item><item><title>Bob Gunderson Tribute</title><link>https://jasoneckert.github.io/myblog/bob-gunderson/</link><description>&lt;p&gt;&lt;img alt="Orion, Bob, and I" src="bob.jpg#right" title="Orion, Bob, and I" /&gt;
The top three software developers that ever worked for triOS College are shown here from left to right: Orion, Bob, and me. Unfortunately, this week we lost Bob to a courageous and difficult battle with cancer.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve known and worked with Bob Gunderson since the beginning of triOS College 25 years ago, when it was called triOS Career Academy and triOS College of Information Technology. He was a talented software developer who taught our early web and software development courses before becoming the Faculty Head, and eventually the senior developer of our own internal software system (called XCAS).&lt;/p&gt;</description><author>Jason Eckert's Website and Blog</author><pubDate>Sun, 01 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jasoneckert.github.io/myblog/bob-gunderson/</guid></item><item><title>Intercepting and modifying Linux system calls with ptrace</title><link>http://notes.eatonphil.com/2023-10-01-intercepting-and-modifying-linux-system-calls-with-ptrace.html</link><description>&lt;p&gt;How software fails is interesting. But real-world errors can be
infrequent to manifest. &lt;a href="https://course.ece.cmu.edu/~ece749/docs/faultInjectionSurvey.pdf"&gt;Fault
injection&lt;/a&gt;
is a formal-sounding term that just means: trying to explicitly
trigger errors in the hopes of discovering bad logic, typically
during automated tests.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/jepsen-io/jepsen"&gt;Jepsen&lt;/a&gt;
and &lt;a href="https://github.com/Netflix/chaosmonkey"&gt;ChaosMonkey&lt;/a&gt; are two
famous examples that help to trigger process and network failure. But
what about disk and filesystem errors?&lt;/p&gt;
&lt;p&gt;A few avenues seem worth investigating:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A custom FUSE filesystem&lt;/li&gt;
&lt;li&gt;An LD_PRELOAD interception layer&lt;/li&gt;
&lt;li&gt;A ptrace system call interception layer&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;SECCOMP_RET_TRAP&lt;/code&gt; interception layer&lt;/li&gt;
&lt;li&gt;Or, symbolic analysis a la &lt;a href="https://research.cs.wisc.edu/adsl/Publications/alice-osdi14.html"&gt;Alice from University of
Wisconsin-Madison&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I would like to try out FUSE sometime. But LD_PRELOAD layer only works
if IO goes through libc, which won't be the case for all
programs. ptrace is something I've wanted to dig into for years since
learning about
&lt;a href="https://www.usenix.org/system/files/hotcloud19-paper-young.pdf"&gt;gvisor&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SECCOMP_RET_TRAP&lt;/code&gt; doesn't have the same high-level guides that ptrace
does so maybe I'll dig into it later. And symbolic analysis might be
able to detect bad workloads but it also isn't fault injection. Maybe
it's the better idea but fault injection just sounds more fun.&lt;/p&gt;
&lt;p&gt;So this particular post will cover intercepting system calls
(syscalls) using ptrace with code written in Zig. Not because readers
will likely write their own code in Zig but because hopefully the Zig
code will be easier for you to read and adapt to your language
compared to if we had to deal with the verbosity and inconvenience of
C.&lt;/p&gt;
&lt;p&gt;In the end, we'll be able to intercept and force short (incomplete)
writes in a Go, Python, and C program. Emulating a disk that is having
an issue completing the write. This is a case that isn't common, but
should probably be handled with retries in production code.&lt;/p&gt;
&lt;p&gt;This post corresponds roughly to &lt;a href="https://github.com/eatonphil/badio/tree/720c3ee0482e6dcb1dd49d1789bccf86747b7776"&gt;this
commit&lt;/a&gt;
on GitHub.&lt;/p&gt;
&lt;h3 id="a-bad-program"&gt;A bad program&lt;/h3&gt;&lt;p&gt;First off, let's write some code for a program that would exhibit a
short write. Basically, we write to a file and don't check how many
bytes we wrote. This is extremely common code; or at least I've
written it often.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;
&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;os&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OpenFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;test.txt&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;O_RDWR&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;O_CREATE&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;O_TRUNC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mo"&gt;0755&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;some great stuff&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With this code, if the &lt;code&gt;Write()&lt;/code&gt; call doesn't actually succeed in
writing everything, we won't know that. And the file will contain less
than all of &lt;code&gt;some great stuff&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This logical mistake will happen rarely, if ever, on a normal
disk. But it is possible.&lt;/p&gt;
&lt;p&gt;Now that we've got an example program in mind, let's see if we can
trigger the logic error.&lt;/p&gt;
&lt;h3 id="ptrace"&gt;ptrace&lt;/h3&gt;&lt;p&gt;ptrace is a somewhat cross-platform layer that allows you to intercept
syscalls in a process. You can read and modify memory and registers in
the process, when the syscalls starts and before it finishes.&lt;/p&gt;
&lt;p&gt;gdb and strace both use ptrace for their magic.&lt;/p&gt;
&lt;p&gt;Google's gvisor that &lt;a href="https://cloud.google.com/run/docs/container-contract"&gt;powers various serverless runtimes in Google
Cloud&lt;/a&gt; was also
historically based on ptrace (&lt;code&gt;PTRACE_SYSEMU&lt;/code&gt; specifically, which we
won't explore much in this post).&lt;/p&gt;
&lt;p class="note"&gt;
  Interestingly though,
  gvisor &lt;a href="https://gvisor.dev/blog/2023/04/28/systrap-release/"&gt;switched
  only this year &lt;/a&gt; (2023) to a different default backend for
  trapping system calls. Based
  on &lt;a href="https://www.kernel.org/doc/Documentation/prctl/seccomp_filter.txt"&gt;&lt;code&gt;SECCOMP_RET_TRAP&lt;/code&gt;&lt;/a&gt;.
  &lt;br /&gt;
  &lt;br /&gt;
  You can get similar vibes
  from &lt;a href="https://www.brendangregg.com/blog/2014-05-11/strace-wow-much-syscall.html"&gt;this
  Brendan Gregg post&lt;/a&gt; on the dangers of using strace (that is based
  on ptrace) in production.
&lt;/p&gt;&lt;p&gt;Although ptrace is cross-platform, actually writing
cross-platform-aware code with ptrace can be complex. So this post
assumes amd64/linux.&lt;/p&gt;
&lt;h3 id="protocol"&gt;Protocol&lt;/h3&gt;&lt;p&gt;The ptrace protocol is described in the &lt;a href="https://man7.org/linux/man-pages/man2/ptrace.2.html"&gt;ptrace
manpage&lt;/a&gt;, but
&lt;a href="https://nullprogram.com/blog/2018/06/23/"&gt;Chris Wellons&lt;/a&gt; and &lt;a href="https://webdocs.cs.ualberta.ca/~paullu/C498/meng.ptrace.slides.pdf"&gt;a
University of Alberta
group&lt;/a&gt;
also wrote nice introductions. I referenced these three pages
heavily.&lt;/p&gt;
&lt;p&gt;Here's what the UAlberta page has to say:&lt;/p&gt;
&lt;p&gt;&lt;img alt="ptrace's syscall tracing protocol" src="/assets/ptraceprotocol.webp" /&gt;&lt;/p&gt;
&lt;p&gt;We fork and have the child call &lt;code&gt;PTRACE_TRACEME&lt;/code&gt;. Then we handle each
syscall entrance by calling &lt;code&gt;PTRACE_SYSCALL&lt;/code&gt; and waiting with &lt;code&gt;wait&lt;/code&gt;
until the child has entered the syscall. It is in this moment we can
mess with things.&lt;/p&gt;
&lt;h3 id="implementation"&gt;Implementation&lt;/h3&gt;&lt;p&gt;Let's turn that graphic into Zig code.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;std&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@cImport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;@cInclude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;sys/ptrace.h&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;@cInclude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;sys/user.h&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;@cInclude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;sys/wait.h&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;@cInclude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;errno.h&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cNullPtr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?*&lt;/span&gt;&lt;span class="n"&gt;anyopaque&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// TODO //&lt;/span&gt;

&lt;span class="kr"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArenaAllocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;page_allocator&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deinit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argsAlloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fork&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Fork failed!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{});&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Child process&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ptrace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PTRACE_TRACEME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cNullPtr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cNullPtr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;..],&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Parent process&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;childPid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waitpid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;childPid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ChildManager&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childPid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;childPid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childInterceptSyscalls&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So like the graphic suggested, we fork and start a child process. That
means this Zig program should be called like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;zig&lt;span class="w"&gt; &lt;/span&gt;build-exe&lt;span class="w"&gt; &lt;/span&gt;--library&lt;span class="w"&gt; &lt;/span&gt;c&lt;span class="w"&gt; &lt;/span&gt;main.zig
$&lt;span class="w"&gt; &lt;/span&gt;./main&lt;span class="w"&gt; &lt;/span&gt;/actual/program/to/intercept&lt;span class="w"&gt; &lt;/span&gt;--and&lt;span class="w"&gt; &lt;/span&gt;--its&lt;span class="w"&gt; &lt;/span&gt;args
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Presumably, as with strace or gdb, we could instead attach to an
already running process with &lt;code&gt;PTRACE_ATTACH&lt;/code&gt; or &lt;code&gt;PTRACE_SEIZE&lt;/code&gt; (based
on the &lt;a href="https://man7.org/linux/man-pages/man2/ptrace.2.html"&gt;ptrace
manpage&lt;/a&gt;) rather
than going the &lt;code&gt;PTRACE_TRACEME&lt;/code&gt; route. But I haven't tried that out
yet.&lt;/p&gt;
&lt;p&gt;With the child ready to be intercepted, we can implement the
&lt;code&gt;ChildManager&lt;/code&gt; that actually does the interception.&lt;/p&gt;
&lt;h4 id="childmanager"&gt;ChildManager&lt;/h4&gt;&lt;p&gt;The core of the &lt;code&gt;ChildManager&lt;/code&gt; is an infinite loop (at least as long
as the child process lives) that waits for the next syscall and then
calls a hook for the sytem call if it exists.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ChildManager&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArenaAllocator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;childPid&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid_t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// TODO //&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;childInterceptSyscalls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ChildManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// Handle syscall entrance&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childWaitForSyscall&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;W&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IFEXITED&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getABIArguments&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Later we'll write a hook for the &lt;code&gt;sys_write&lt;/code&gt; syscall that
will force an incomplete write.&lt;/p&gt;
&lt;p&gt;Back to the protocol, &lt;code&gt;childWaitForSyscall&lt;/code&gt; will call &lt;code&gt;PTRACE_SYSCALL&lt;/code&gt;
to allow the child process to start up again and continue until the
next syscall. We'll follow that by &lt;code&gt;wait&lt;/code&gt;-ing for the child
process to be stopped again so we can handle the syscall entrance.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;childWaitForSyscall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ChildManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;i32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ptrace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PTRACE_SYSCALL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childPid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cNullPtr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cNullPtr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waitpid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childPid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@bitCast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now that we've intercepted a syscall (after &lt;code&gt;waitpid&lt;/code&gt; finishes
blocking), we need to figure out what syscall it was. We do this by
calling &lt;code&gt;PTRACE_GETREGS&lt;/code&gt; and reading the &lt;code&gt;rax&lt;/code&gt; register which
according to &lt;a href="https://stackoverflow.com/a/54957101/1507139"&gt;amd64/linux calling
convention&lt;/a&gt; is the
syscall called.&lt;/p&gt;
&lt;h4 id="registers"&gt;Registers&lt;/h4&gt;&lt;p&gt;&lt;code&gt;PTRACE_GETREGS&lt;/code&gt; fills out the &lt;a href="https://sites.uclouvain.be/SystInfo/usr/include/sys/user.h.html"&gt;following
struct&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;user_regs_struct&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r15&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r14&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r13&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r12&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rbp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rbx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r11&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rax&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rcx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rdx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rsi&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rdi&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;orig_rax&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rip&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;eflags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ss&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fs_base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gs_base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;es&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's write a little amd64/linux-specific wrapper for accessing
meaningful fields.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_regs_struct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;c_ulong&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rdi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rsi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rdx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unreachable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;setNth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;c_ulong&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rdi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rsi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rdx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unreachable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;c_ulong&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rax&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;setResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;c_ulong&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rax&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;c_ulong&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;orig_rax&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;One thing to note is that the field we read to get &lt;code&gt;rax&lt;/code&gt; is not
&lt;code&gt;aa.regs.rax&lt;/code&gt; but &lt;code&gt;aa.regs.orig_rax&lt;/code&gt;. This is because &lt;code&gt;rax&lt;/code&gt; is also
the return value and &lt;code&gt;PTRACE_SYSCALL&lt;/code&gt; gets called twice for some
syscalls on entrance and exit. The &lt;code&gt;orig_rax&lt;/code&gt; field preserves the
original &lt;code&gt;rax&lt;/code&gt; value on syscall entrance. You can read more about this
&lt;a href="https://stackoverflow.com/questions/6468896/why-is-orig-eax-provided-in-addition-to-eax/6469069#6469069"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="getting-and-setting-registers"&gt;Getting and setting registers&lt;/h4&gt;&lt;p&gt;Now let's write the &lt;code&gt;ChildManager&lt;/code&gt; code that actually calls
&lt;code&gt;PTRACE_GETREGS&lt;/code&gt; to fill out one of these structs.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;getABIArguments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ChildManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ptrace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PTRACE_GETREGS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childPid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cNullPtr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Setting registers is similar, we just pass the struct back and call
&lt;code&gt;PTRACE_SETREGS&lt;/code&gt; instead:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;setABIArguments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ChildManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ptrace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PTRACE_SETREGS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childPid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cNullPtr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id="a-hook"&gt;A hook&lt;/h4&gt;&lt;p&gt;Now we finally have enough code to write a hook that can get and set
registers; i.e. manipulate a system call!&lt;/p&gt;
&lt;p&gt;We'll start by registering a &lt;code&gt;sys_write&lt;/code&gt; hook in the &lt;code&gt;hooks&lt;/code&gt; field we
check in &lt;code&gt;childInterceptSyscalls&lt;/code&gt; above.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;c_ulong&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ChildManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;anyerror&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}{.{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@intFromEnum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linux&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;syscalls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;X64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;writeHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}};&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If we look at the &lt;a href="https://man7.org/linux/man-pages/man2/write.2.html"&gt;manpage for
&lt;code&gt;write&lt;/code&gt;&lt;/a&gt; we see it
takes three arguments&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The file descriptor (fd) to write to&lt;/li&gt;
&lt;li&gt;The address to start writing data from&lt;/li&gt;
&lt;li&gt;And the number of bytes to write&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Going back to the &lt;a href="https://stackoverflow.com/questions/2535989/what-are-the-calling-conventions-for-unix-linux-system-calls-and-user-space-f"&gt;calling
convention&lt;/a&gt;
that means the fd will be in &lt;code&gt;rdi&lt;/code&gt;, the data address in &lt;code&gt;rsi&lt;/code&gt;, and the
data length in &lt;code&gt;rdx&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So if we shorten the data length, we should be creating a short
(incomplete) write.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;writeHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ChildManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;anyerror&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dataAddress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Truncate some bytes&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setNth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setABIArguments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In a more sophisticated version of this program, we could randomly
decide when to truncate data and randomly decide how much data to
truncate. However, for our purposes this is sufficient.&lt;/p&gt;
&lt;p&gt;But there are some real problems with this code. When I ran this
program against a basic Go program, I saw duplicate requests.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Ah ok, PTRACE_SYSCALL gets hit when you both enter and exit a syscall.&lt;br /&gt;&lt;br /&gt;So each time you call PTRACE_SYSCALL and you do stuff, you just call it again afterwards to handle/wait for the exit. &lt;a href="https://t.co/PjmNwcMepG"&gt;pic.twitter.com/PjmNwcMepG&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1707846783035183267?ref_src=twsrc%5Etfw"&gt;September 29, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;
&lt;p&gt;So the deal with &lt;code&gt;PTRACE_SYSCALL&lt;/code&gt; is that for (most?) syscalls, you
get to modify data before the data actually is handled by the
kernel. And you get to modify data after the kernel has finished the
syscall too.&lt;/p&gt;
&lt;p&gt;This makes sense because &lt;code&gt;PTRACE_SYSCALL&lt;/code&gt; (unlike &lt;code&gt;PTRACE_SYSEMU&lt;/code&gt;)
allows the syscall to actually happen. And if we wanted to, for
example, modify the syscall exit code, we'd have to do that after the
syscall was done not before it started. We are modifying registers
directly after all.&lt;/p&gt;
&lt;p&gt;All this means for our Zig code is that when we handle &lt;code&gt;sys_write&lt;/code&gt;, we
need to call &lt;code&gt;PTRACE_SYSCALL&lt;/code&gt; again to process the syscall
exit. Otherwise we'd reach this &lt;code&gt;writeHandler&lt;/code&gt; for both entries and
exits, which would require some additional way of disambiguating
entrances from exits.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;writeHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ChildManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;anyerror&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dataAddress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Truncate some bytes&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setNth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setABIArguments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childReadData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deinit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Got a write on {}: {s}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Handle syscall exit&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childWaitForSyscall&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p class="note"&gt;
  We could put the &lt;code&gt;cm.childWaitForSyscall()&lt;/code&gt; waiting for
  the syscall exit in the main loop and I did try that at
  first. However, not all syscalls seemed to have the same entry and
  exit hook and this resulted in the hooks sometimes starting with a
  syscall exit rather than a syscall entry. So rather than making the
  code more complicated, I decided to only wait for the exit on
  syscalls I knew had an exit (by observation at least), like
  &lt;code&gt;sys_write&lt;/code&gt;.
&lt;/p&gt;&lt;h3 id="multiple-writes?-no-bad-logic?"&gt;Multiple writes? No bad logic?&lt;/h3&gt;&lt;p&gt;So I had this code as is, correctly handling syscall entrances and
exits, but I was seeing multiple write calls. And the text file I was
writing to had the complete text I wanted to write. There was no short
write even though I truncated the data length.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Ok so what happens in this Go program if I truncate the amount of data?&lt;br /&gt;&lt;br /&gt;I assumed Go would do nothing since all I did was call `f.Write()` once and `f.Write()` returns a number of bytes written.&lt;br /&gt;&lt;br /&gt;But actually, it still writes everything! &lt;a href="https://t.co/OSalKEbERM"&gt;pic.twitter.com/OSalKEbERM&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1707854642250408119?ref_src=twsrc%5Etfw"&gt;September 29, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;
&lt;p&gt;This took some digging into Go source code to understand. If you trace
what &lt;code&gt;os.File.Write()&lt;/code&gt; does on Linux you eventually get to
&lt;a href="https://cs.opensource.google/go/go/+/refs/tags/go1.21.1:src/internal/poll/fd_unix.go"&gt;src/internal/poll/fd_unix.go&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;// Write implements io.Writer.&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;FD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeLock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeUnlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prepareWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isFile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IsStream&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;nn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;maxRW&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;maxRW&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ignoringEINTRIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sysfd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="nx"&gt;nn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EAGAIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pollable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isFile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                                &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ErrUnexpectedEOF&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This might be common knowledge but I didn't realize Go did this. And
when I tried out the same basic program in Python and even C, the
behavior was the same. The builtin &lt;code&gt;write()&lt;/code&gt; behavior on a file (in
many languages apparantly) is to retry until all data is written, with
some exceptions.&lt;/p&gt;
&lt;p&gt;This makes sense since files on disk, unlike file descriptors backed
by network sockets, are generally always available. Compared to a
network connection, disks are physically close and almost always
stay connected. (With some obvious exceptions like
network-attached storage and thumb drives.)&lt;/p&gt;
&lt;p&gt;So to trigger the short write, the easiest way seems to have the
&lt;code&gt;sys_write&lt;/code&gt; call return an error that is NOT &lt;code&gt;EAGAIN&lt;/code&gt; since the code
will retry if that is the error.&lt;/p&gt;
&lt;p&gt;After looking through the &lt;a href="https://man7.org/linux/man-pages/man2/write.2.html#ERRORS"&gt;list of errors that sys_write can
return&lt;/a&gt;,
&lt;code&gt;EIO&lt;/code&gt; seems like a nice one.&lt;/p&gt;
&lt;p&gt;So let's do our final version of &lt;code&gt;writeHandler&lt;/code&gt; and on the syscall
exit, we'll modify the return value (&lt;code&gt;rax&lt;/code&gt; in amd64/linux) to be
&lt;code&gt;EIO&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;writeHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ChildManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;anyerror&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dataAddress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Truncate some bytes&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setNth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setABIArguments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Handle syscall exit&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childWaitForSyscall&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exitArgs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getABIArguments&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exitArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// Force the writes to stop after the first one by returning EIO.&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;c_ulong&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EIO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;exitArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setABIArguments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;exitArgs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's give it a whirl!&lt;/p&gt;
&lt;h3 id="all-together"&gt;All together&lt;/h3&gt;&lt;p&gt;Build the Zig fault injector and the Go test code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;zig&lt;span class="w"&gt; &lt;/span&gt;build-exe&lt;span class="w"&gt; &lt;/span&gt;--library&lt;span class="w"&gt; &lt;/span&gt;c&lt;span class="w"&gt; &lt;/span&gt;main.zig
&lt;span class="gp"&gt;$ &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;go&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;main.go&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;./main&lt;span class="w"&gt; &lt;/span&gt;test/main
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And check &lt;code&gt;test.txt&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;test.txt
some&lt;span class="w"&gt; &lt;/span&gt;great&lt;span class="w"&gt; &lt;/span&gt;stu
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Hey, that's a short write! :)&lt;/p&gt;
&lt;h3 id="sidenote:-reading-data-from-the-child"&gt;Sidenote: Reading data from the child&lt;/h3&gt;&lt;p&gt;We accomplished everything we set out to, but there's one other useful
thing we can do: reading the actual data passed to the write syscall.&lt;/p&gt;
&lt;p&gt;Just like how we can get the child process registers with
&lt;code&gt;PTRACE_GETREGS&lt;/code&gt;, we can read child memory with
&lt;code&gt;PTRACE_PEEKDATA&lt;/code&gt;. &lt;code&gt;PTRACE_PEEKDATA&lt;/code&gt; takes the child process id and
the memory address in the child to read from. It returns a word of
data (which on amd64/linux is 8 bytes).&lt;/p&gt;
&lt;p&gt;We can use the syscall arguments (data address and length) to keep
calling &lt;code&gt;PTRACE_PEEKDATA&lt;/code&gt; on the child until we've read all bytes of
the data the child process wanted to write:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;childReadData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ChildManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;c_ulong&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;c_ulong&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArrayList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArrayList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ptrace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PTRACE_PEEKDATA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childPid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;cNullPtr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;asBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And we could modify &lt;code&gt;writeHandler&lt;/code&gt; to print out the entirety of the write message each time (for debugging):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;writeHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ChildManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;anyerror&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dataAddress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Truncate some bytes&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setNth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setABIArguments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childReadData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deinit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Got a write on {}: {s}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Handle syscall exit&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childWaitForSyscall&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exitArgs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getABIArguments&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exitArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// Force the writes to stop after the first one by returning EIO.&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;c_ulong&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EIO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;exitArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setABIArguments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;exitArgs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That's pretty neat!&lt;/p&gt;
&lt;h3 id="next-steps"&gt;Next steps&lt;/h3&gt;&lt;p&gt;Short writes are just one of many bad IO interactions. Another fun one
would be to completely buffer all writes on a file descriptor (not
allowing anything to be written to disk at all) until fsync is called
on the file descriptor. Or &lt;a href="https://www.usenix.org/conference/atc20/presentation/rebello"&gt;forcing fsyncs to
fail&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;An interesting optimization would be to apply &lt;a href="https://www.kernel.org/doc/Documentation/prctl/seccomp_filter.txt"&gt;seccomp
filters&lt;/a&gt;
so that rather than paying a penalty for watching every syscall, I
only get notified about the ones I have hooks for like
&lt;code&gt;sys_write&lt;/code&gt;. &lt;a href="https://www.alfonsobeato.net/c/filter-and-modify-system-calls-with-seccomp-and-ptrace/"&gt;Here's another
post&lt;/a&gt;
that explores ptrace with seccomp filters.&lt;/p&gt;
&lt;p&gt;Credits: Thank you Charlie Cummings and Paul Khuong for reviewing a draft
of this post!&lt;/p&gt;
&lt;h3 id="selected-responses-after-publication"&gt;Selected responses after publication&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;oscooter on Reddit &lt;a href="https://www.reddit.com/r/linux/comments/16x32l3/comment/k380m9q/?utm_source=reddit&amp;amp;utm_medium=web2x&amp;amp;context=3"&gt;gave some
tips&lt;/a&gt;
on using ptrace, including using &lt;code&gt;process_vm_readv&lt;/code&gt; instead of
&lt;code&gt;PTRACE_PEEKDATA&lt;/code&gt; to read memory from the tracee process.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Fault injection is a scary-sounding term. Intercepting and modifying Linux system calls sounds scary too.&lt;br /&gt;&lt;br /&gt;But it's a neat way to trigger logical errors in programs, to build confidence we wrote code correctly.&lt;br /&gt;&lt;br /&gt;Let's trigger short writes to disk in Zig!&lt;a href="https://t.co/0C3tWt3vtT"&gt;https://t.co/0C3tWt3vtT&lt;/a&gt; &lt;a href="https://t.co/OS7auDe8jR"&gt;pic.twitter.com/OS7auDe8jR&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1708482934863180004?ref_src=twsrc%5Etfw"&gt;October 1, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Sun, 01 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2023-10-01-intercepting-and-modifying-linux-system-calls-with-ptrace.html</guid></item><item><title>Fine-Tuning Glue Export File Size for Athena Queries</title><link>https://www.vladsiv.com/posts/glue-optimal-filesize</link><description>Exploring different strategies for fine-tuning the output file size in AWS Glue and consolidating small files during post-processing. By implementing these techniques, you'll not only enhance the efficiency of Athena queries but also significantly reduce the cost associated with querying large datasets.</description><author>vladsiv</author><pubDate>Sun, 01 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.vladsiv.com/posts/glue-optimal-filesize</guid></item><item><title>About</title><link>https://nikhilism.com/about/</link><description>I currently work at Skydio.
I write about my travels at Life in High Places.
My CV.
All opinions and thoughts on this website are my own and do not represent the views of my employer or anyone else.
Colophon This blog is powered by Hugo and the Archie theme.</description><author>Nikhil's blog</author><pubDate>Sun, 01 Oct 2023 01:04:41 GMT</pubDate><guid isPermaLink="true">https://nikhilism.com/about/</guid></item><item><title>October 2023 - Bookmarks</title><link>https://domenicoluciani.com/2023/10/01/bookmarks.html</link><description>Bookmarks for October 2023: 4 links - The Absolute Minimum Every Software Develo...; Dependency Injection - Learn Go with tests, and more.</description><author>Domenico Luciani</author><pubDate>Sun, 01 Oct 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://domenicoluciani.com/2023/10/01/bookmarks.html</guid></item><item><title>Lost in the network</title><link>https://deadlime.hu/en/2023/09/30/lost-in-the-network/</link><description>A deep dive into the intriguing world of network packets</description><author>deadlime</author><pubDate>Sat, 30 Sep 2023 22:58:42 GMT</pubDate><guid isPermaLink="true">https://deadlime.hu/en/2023/09/30/lost-in-the-network/</guid></item><item><title>A reflection on my Software Engineering degree.</title><link>https://headlinedev.xyz/2023/09/30/a-reflection-on-my-software-engineering-degree.html</link><description>A reflection on my Software Engineering degree. In May 2023 I graduated with a bachelor’s degree in software engineering from a California State University (CSU) school. Since then, I’ve been hired as a network programmer for a game company, where I primarily work on multiplayer stuff. Although I’ve had some great experiences modding games up till now, I was overall skeptical of entering the video game industry.</description><author>headlinedev</author><pubDate>Sat, 30 Sep 2023 10:00:00 GMT</pubDate><guid isPermaLink="true">https://headlinedev.xyz/2023/09/30/a-reflection-on-my-software-engineering-degree.html</guid></item><item><title>8.8.8.8</title><link>https://ankitag9.substack.com/p/8888</link><description>Why does Google own DNS, Chrome and Google Analytics</description><author>Perspective by Ankit Agarwal</author><pubDate>Sat, 30 Sep 2023 09:57:00 GMT</pubDate><guid isPermaLink="true">https://ankitag9.substack.com/p/8888</guid></item><item><title>Symmetry factor of Feynman diagrams</title><link>https://diffxweyl.wordpress.com/2023/09/29/symmetry-factor-of-feynman-diagrams/</link><description>Symmetry factors are often confusing, and existing calculation techniques seem to involve excessive drawings and computations. My personal technique is simple: For example, this diagram has , since we can interchange and at the same time, and get the same diagram (assuming the endpoints are fixed). This one has , coming from With a bit &amp;#8230; &lt;a class="more-link" href="https://diffxweyl.wordpress.com/2023/09/29/symmetry-factor-of-feynman-diagrams/"&gt;Continue reading&lt;span class="screen-reader-text"&gt; "Symmetry factor of Feynman&amp;#160;diagrams"&lt;/span&gt;&lt;/a&gt;</description><author>diff × weyl</author><pubDate>Sat, 30 Sep 2023 08:50:29 GMT</pubDate><guid isPermaLink="true">https://diffxweyl.wordpress.com/2023/09/29/symmetry-factor-of-feynman-diagrams/</guid></item><item><title>The Photo Game: Animals</title><link>https://andrewbridges.org/the-photo-game-animals/</link><description>This week, I visited a high school to watch a gymnastics meet. Before I went, I expected to see feral teenagers, acting like animals. I imagined it would be like the scene from Mean Girls, with lots of growling, screeching, and snorting.</description><author>Show Notes</author><pubDate>Sat, 30 Sep 2023 08:16:21 GMT</pubDate><guid isPermaLink="true">https://andrewbridges.org/the-photo-game-animals/</guid></item><item><title>My Experience With Spinal Fusion</title><link>https://zdwolfe.medium.com/my-experience-with-spinal-fusion-fc117fe2f5fe?source=rss-65d40a7904f------2</link><description>&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zHMbvyuOCncHYGUvoNQASA.jpeg" /&gt;&lt;figcaption&gt;Photo by &lt;a href="https://unsplash.com/photos/IG96K_HiDk0"&gt;Joyce McCown&lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;h3&gt;My Spinal Fusion Experience&lt;/h3&gt;&lt;p&gt;In May 2021 I had a lumbar inter-body fusion at 30 years old to address a 15-year recurring disc herniation between my L5/S1 vertebrae.&lt;/p&gt;&lt;p&gt;Going into the surgery I found information about the procedure was either too technical or academic to easily understand, too sales-oriented for a particular device or procedure or hospital, or in video-anecdote-form on YouTube, so I felt compelled to write about my personal experience.&lt;/p&gt;&lt;p&gt;Below is some background, a description of the procedure I had, my experience recovering after surgery, as well as some tips I picked up and mistakes I made with the hopes of making it easier to manage for others considering a similar procedure. In a few months I’ll add more to the story after (hopefully) recovering more completely.&lt;/p&gt;&lt;blockquote&gt;None of this is medical advice, most is just therapeutic journaling.&lt;/blockquote&gt;&lt;h3&gt;TL;DR&lt;/h3&gt;&lt;p&gt;If you get bored and don’t finish the whole post, take away the following highlights/recommendations:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;I had a recurring disc herniation due to degeneration, which was doomed to disrupt my life regularly for years until it naturally fused. Surgical fusion was agreed upon by all three doctors I consulted.&lt;/li&gt;&lt;li&gt;I could walk a tiny bit the day after surgery. Within a week I could walk for 30m before I had too much pain. Within two weeks I could walk for an hour and felt stronger every day.&lt;/li&gt;&lt;li&gt;I could not do basic housework, prepare food, or even manage pain for the first week without my wife helping me with literally everything, even getting in and out of bed.&lt;/li&gt;&lt;li&gt;I used a walker extensively for the first week, and a claw-style grabber for the first two weeks.&lt;/li&gt;&lt;li&gt;I messed around with bed pillows a ton until I found a position where my legs and back were comfortable. I wish I had just bought a wedge pillow.&lt;/li&gt;&lt;li&gt;A toilet seat riser and safety rails were very helpful as I couldn’t bend with my torso at all for some time.&lt;/li&gt;&lt;li&gt;I could not twist, bend, or lift anything more 5 pounds for two weeks, and had a lot of trouble reaching up. I couldn’t bend enough to spit toothpaste when brushing for a week.&lt;/li&gt;&lt;li&gt;The “Log roll method” of getting in/out of bed was really important. When I forgot or was lazy and twisted even a little it hurt like hell. For the first four days, my wife had to lift my legs because I couldn’t pull them up onto the bed myself.&lt;/li&gt;&lt;li&gt;I rapidly recovered in the first twenty days from 1/10 to 6.5/10.&lt;/li&gt;&lt;li&gt;Two years later, I’ve recovered about 90% of my typical body/athletic ability. See the last section for a brief update.&lt;/li&gt;&lt;/ol&gt;&lt;h3&gt;Nerve pain history&lt;/h3&gt;&lt;p&gt;I describe my pain as persistent stabbing, tingling, shooting, and throbbing that would radiate from my low back just above my belt, down through my thigh and into the lateral left calf muscle. My left calf muscle would twitch, spasm, flutter, and was overall very distracting all the time.&lt;/p&gt;&lt;p&gt;At its worst, I developed a limp and &lt;a href="https://www.webmd.com/a-to-z-guides/foot-drop-causes-symptoms-treatments"&gt;foot drop&lt;/a&gt; due to near-zero strength in my left calf and ankle. My left foot would feel cold itself but warm to my hands (very weird) and its touch sensation was muffled or muted as if I was wearing five pairs of socks at the same time. Sitting or standing for long periods would exacerbate symptoms enough to bring tears. I would frequently wake up yelling about some monster in my dreams that was gnawing on my left leg, only to find my calf had turned into a knotted baseball-sized mess.&lt;/p&gt;&lt;p&gt;The pain almost perfectly followed the red/yellow L5 and S1 dermatomes below:&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/126/1*KqFbmxdn3W9UOwy_RlHCUA.png" /&gt;&lt;figcaption&gt;(&lt;a href="https://en.wikipedia.org/wiki/Dermatome_(anatomy)#/media/File:Grant_1962_663.png"&gt;source&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;I first developed symptoms in 2008 at age 15 with no obvious cause, though I can recall falling from a height of 15–20 feet and getting in a violent pick-up football tackle the year or two before. Every few years since, I cycled through periods of relatively no pain to major flare-ups requiring strong pain killers, physical therapy or surgery. Each noted date below was accompanied by months of being mostly dysfunctional, missing class, work, and sleep.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/726/1*UEQYvs60Y9kAz4dCy5CSBg.png" /&gt;&lt;figcaption&gt;2008: PT and Microdiscectamy, 2014: Microdiscectamy, 2019: PT and PKs, 2021: Fusion, PKs, PT&lt;/figcaption&gt;&lt;/figure&gt;&lt;h3&gt;Symptoms prompting fusion in 2021&lt;/h3&gt;&lt;p&gt;In 2021 my nerve pain symptoms deteriorated to the worst and most painful they have ever been, by far.&lt;/p&gt;&lt;p&gt;I was down in bed in mid-March on my back with ice packs and painkillers. From March 20th through May I was in bed almost all day every day, leaving bed only for doctor’s appointments and bio-necessaries. Standing for more than a few minutes was excruciating and would bring me to tears.&lt;/p&gt;&lt;p&gt;In the below graph, my steps activity graph (blue) shows this pretty clearly — I went on my 5-mile run and 3–4 mile walks the next few days (which is a typical day for me), then my activity fell off a cliff. Once I realized I had herniated my disc again I started logging my pain (pink) on a 1–10 scale. Through all of April, I rarely breached 1,000 steps, which is essentially walking around the house a few times in a day.&lt;/p&gt;&lt;a href="https://medium.com/media/2a987ea8743b5da1fd70e26aefec11ca/href"&gt;https://medium.com/media/2a987ea8743b5da1fd70e26aefec11ca/href&lt;/a&gt;&lt;p&gt;I went to a sports medicine doctor at the University of Washington I had dealt with before and was prescribed an MRI looking for a herniated L5/S1 as I had in years before. I was told by the doctor and his colleague that I had the biggest herniation they had ever seen (yay), and it’s pretty obvious from the image. I was referred to a surgeon for urgent surgery.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/290/1*0DbuJBCCZ9AmoeS32ufuuQ.png" /&gt;&lt;figcaption&gt;My lumbar MRI showing a herniated L5/S1 (dark grey blob lower right)&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/354/1*C4ZFZlATDDCiL4eDIUzX2g.png" /&gt;&lt;figcaption&gt;The herniated portion of disc (center dark grey oval) pinching my nerve root (dark grey blob pinching white nerves)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;For reference, the L5/S1 is the white area between the lowest lumbar vertebrae and the sacrum near the hips:&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/464/1*kXiDP3eLiF184Q_tcDMTyg.png" /&gt;&lt;figcaption&gt;(&lt;a href="https://upload.wikimedia.org/wikipedia/commons/2/2f/Lumbar_vertebrae_animation4.gif"&gt;source&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;I visited two surgeons over the next two weeks, both of which agreed on fusion.&lt;/p&gt;&lt;h3&gt;Decision for fusion over other options&lt;/h3&gt;&lt;p&gt;When I was trying to figure out what to do to relieve my back pain, I had a few options:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Option 1: Physical Therapy Only&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;This option would just be to go to PT for a few months and manage pain with strong pain killers. This was attractive because I’d recovered from a herniated disc from surgery twice already, but also once before just by using a few months of PT and PKs. I chose not to do this for a few reasons. First, my pain was bad enough that I felt like I needed relief sooner than I was willing to wait with PT, so I was biased for a surgical option. Second, I was hurting so bad that I could not sit, stand, or walk long enough to even get to a PT’s office.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Option 2: A Third Microdiscectamy&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;This option would be to have a third microdiscectamy, yet again shaving the herniated part of my disc away that was causing me pain. This was attractive because I’d had the procedure twice before already and understood the out-patient recovery and the pros/cons.&lt;/p&gt;&lt;p&gt;The primary reason I didn’t choose another microdiscectamy was because of a &lt;a href="https://journals.lww.com/jbjsoa/FullText/2018/06000/Patients_at_the_Highest_Risk_for_Reherniation.1.aspx"&gt;study&lt;/a&gt; my surgeon cited that linked the size of ligament tear with risk of re-herniation.&lt;/p&gt;&lt;p&gt;In my case, I now had a “…left subarticular disc extrusion … measuring 14 mm in size..” which is a very large tear. Combined with the fact that I’d already had two microdiscectamies in the past and “..this will probably be [my] sixth disk herniation..”, and yet was still in this situation a few years later, my surgeon and a second opinion agreed that I was extremely likely to repeatedly have re-herniation again and again and again until there wasn’t any disc left, so why bother?&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Option 3: Total Disc Replacement (Artificial Disc)&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;This option would be to have an artificial disc inserted between my L5 and S1 vertebrae, much like an artificial hip. This was attractive because artificial discs allow for a much wider range of motion than other invasive options like fusion.&lt;/p&gt;&lt;p&gt;I opted against this option for a few reasons as well. First, according to my surgeon, this option doesn’t have a major advantage in success rate or patient-reported quality of life compared to standard inter-body fusion, but is carries risks of mechanical failure over time. Second, at the level that I needed correction, L5/S1, there isn’t a lot of movement anyway, so the cost/benefit wasn’t there. If I was dealing with a disc in my upper back or neck, then it would have been a different story.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/222/1*az6JQ5wnhK237NFYWPXhrw.png" /&gt;&lt;figcaption&gt;Example artificial disc&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;strong&gt;Option 4: AxiaLiF&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;AxiaLiF is a form of inter-body fusion that is the least-invasive. Colloquially called “the butt screw,” a big titanium screw would be driven upwards from the tailbone area, fusing the vertebrae together. My surgeon recommended against this as it was associated with increased infection rates over a standard fusion, and was no longer even practiced locally after so many occurrences of complications.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Option 5: Lumbar Interbody Fusion (selected)&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;This was the option ended up going for, described later in the Procedure section.&lt;/p&gt;&lt;h4&gt;Doctor’s Opinion&lt;/h4&gt;&lt;p&gt;I got three opinions that all agreed that fusion would be a good way for more long-lasting relief of my nerve pain symptoms. If you’re considering fusion, I’d definitely recommend getting a second opinion because while each doctor agreed, they all had different things to say about it.&lt;/p&gt;&lt;h4&gt;Personal Anecdotes on Recovery from YouTube&lt;/h4&gt;&lt;p&gt;Leading up to surgery, I viewed dozens of YouTube videos of personal testimonials covering the recovery process to prepare myself for what I should expect. A few of my favorites were &lt;a href="https://www.youtube.com/watch?v=VtP-XUOYmHc"&gt;Derek Spratt’s&lt;/a&gt;, &lt;a href="https://www.youtube.com/watch?v=bs3GO1bauYc"&gt;Will Port’s&lt;/a&gt;, and &lt;a href="https://www.youtube.com/watch?v=1_FRDlWjU8o"&gt;Emily Baker’s&lt;/a&gt;, as they followed up with their recovery and challenges periodically over several years post-op, which gave me confidence that I could return to a normal life after surgery.&lt;/p&gt;&lt;p&gt;These were all very helpful and contained a lot of the same tips I mention elsewhere, I’d recommend anyone considering fusion to check these out.&lt;/p&gt;&lt;h3&gt;Risks of Fusion&lt;/h3&gt;&lt;p&gt;The risks with that I could gather were infection, no change in pain, worse pain, paralysis, and death.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Failed Back Syndrome — Unchanged or Worse Back Pain&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;A worrying risk was &lt;a href="https://en.wikipedia.org/wiki/Failed_back_syndrome"&gt;Failed Back Syndrome&lt;/a&gt;, also called “post-laminectomy syndrome.” This risk is essentially that something goes wrong during surgery, or that the surgery doesn’t actually fix the underlying issue. Usually this is caused by a surgeon’s mistake, scar tissue forming in a way that re-aggravates the nerves, a missed portion of the herniation that ends up floating around, blocking the bones from fusing, or poor healing due to other health issues.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Death&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Obviously, the worst risk was death. One &lt;a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC6198620/"&gt;study&lt;/a&gt; I found showed a 0.18% death rate of fusion in the males included in the study. This is just a little less than one in five hundred — which was really worrying! After &lt;a href="https://pubmed.ncbi.nlm.nih.gov/19282796/"&gt;further reading&lt;/a&gt; and discussion with my surgeon, though, I felt better about the risk. The study I read first included people of all age ranges, and varying degrees of health. The median age for the surgery I was having is much higher than my own, and the risk of complications is higher as you age. At my young age and good general health, I would be most likely to die from overdosing on the opioid pain killers prescribed after surgery than any other complication.&lt;/p&gt;&lt;h3&gt;Procedure — &lt;strong&gt;TLIF&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;The procedure I had was a Transforaminal Lumbar Inter-body Fusion (TLIF). I was in the operating room for four hours. I have six incisions — one vertical six-inch incision that re-used and extended a scar from a previous microdiscectamy, four straddling horizontal two-inch incisions, and another small vertical incision on the bottom right. Oddly enough the four horizontal incisions don’t line up vertically with each-other (I’m not sure why).&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/839/1*XmiXtXeehdCxIIYO-OvoQQ.png" /&gt;&lt;/figure&gt;&lt;p&gt;The four two-inch horizontal incisions were each used to insert a total of four 50mm titanium screws tying the L5/S1 together, acting as a cast to keep everything from moving while the bones fuse together.&lt;/p&gt;&lt;p&gt;The large vertical incision was used to perform a laminectomy, which is where the bone that connects to the poke-y ridge you feel when you touch your back to your vertebrae is removed to decompress the nerve (red line below).&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/450/1*LyVbk9i4borV3Hkt6Lhs9Q.png" /&gt;&lt;figcaption&gt;The red bit is removed during a laminectomy&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;The facet joint, which is the joint between the levels of above-mentioned “bony projections” was also removed, the “extremely large extruded [disc] fragment” pinching my nerve was removed, followed by the removal of what was remaining of my L5/S1 disc.&lt;/p&gt;&lt;p&gt;Then a crescent-shaped polyetheretherketone plastic spacer was placed between the two vertebrae along with Infuse bone-growth material to help the bones grow together.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/444/1*_OFdX7219Mv8TTp878QoeQ.png" /&gt;&lt;figcaption&gt;Crescent™ PEEK Vertebral Body Spacer&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;After the spacer was in place, two 40mm rods were placed through the left and right side incisions and torqued into place.&lt;/p&gt;&lt;p&gt;The below X-ray shows the screws, rods, and spacer. The small wire “H” is an X-ray-visible marker within the plastic spacer.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/387/1*Ij4zW1eHXR7JqH3fSzwXVQ.png" /&gt;&lt;figcaption&gt;My X-Ray two weeks after surgery&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/480/1*feygHsBuZDU1WS-lUkI2YA.png" /&gt;&lt;figcaption&gt;My X-Ray two weeks after surgery&lt;/figcaption&gt;&lt;/figure&gt;&lt;h3&gt;Recovery&lt;/h3&gt;&lt;p&gt;I recovered back to a sustainable quality of life way faster than I thought I would, really turning the corner at the two week mark. The first three days was a total blur and an all-around awful experience, but I was able to do more and more every day following that.&lt;/p&gt;&lt;p&gt;Immediately upon waking up from anesthesia I tested if my legs still worked. I was really worried about becoming paralyzed during surgery, but was relieved that they felt fine. I think I told my nurse this about a thousand times as I was coming to.&lt;/p&gt;&lt;p&gt;Then I took note of my left leg that had been causing me pain recently:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;I felt, for the first time in years, absolutely no nerve pain in my leg. It was incredible. The touch sensation in my left foot was much stronger, though not perfect, and it no longer felt cold and distant. The sharp tingling pins and needles in my ankle was gone, and my left calf had stopped spasming.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;I get chills every time I think about that first realization.&lt;/p&gt;&lt;p&gt;I did feel intense pain elsewhere, though, the worst I’ve felt in my life even with IV pain killers and local anesthesia.&lt;/p&gt;&lt;p&gt;My calves were painful due to electromyographic needles inserted into my legs to measure the health of my nerves during the procedure. This faded somewhat after a few days but still bothered me even two weeks later, and was weirdly the last thing to recover fully.&lt;/p&gt;&lt;p&gt;It felt like a horse kicked me in the chest from being laid on my front in a harness for so long, but this went away by the next day.&lt;/p&gt;&lt;p&gt;My lateral left thigh was almost completely numb, to the point of barely sensing someone touching it, but also painful. My surgeon said this was normal as the nerves are stretched by the new spine height that the spacer provides. This mostly recovered within a week, though is still somewhat noticeable two weeks later.&lt;/p&gt;&lt;p&gt;I also had absolutely zero strength in my torso and a lot of pain around the incisions, glutes, and hips. I didn’t get out of bed at all the first day. If I wanted to adjust or roll even slightly while in bed, I needed to use my hands under my hips to “walk” my torso into position. Moving enough use a urinal was extremely painful and difficult.&lt;/p&gt;&lt;h3&gt;Week One At Home&lt;/h3&gt;&lt;p&gt;The first day back at home, the day after surgery, I slept almost all day and just laid in bed taking pain killers every few hours. Days two and three were about the same.&lt;/p&gt;&lt;p&gt;After four days I could get out of bed by myself with a walker to use the restroom. Before that my wife needed to help me get out of bed. I had to move really slowly and with a walker — it took me about ten minutes just to get out of and back into bed.&lt;/p&gt;&lt;p&gt;After five days I could stand up long enough to eat meals.&lt;/p&gt;&lt;p&gt;After six days I was just able to walk very gingerly around one city block with help.&lt;/p&gt;&lt;p&gt;After eight days I was able to go for a half-hour very slow and very flat ten-block walk by myself with a cane. I also was able to sit for ten minutes until it was too uncomfortable.&lt;/p&gt;&lt;p&gt;The below graph shows my average daily pain (pink), mood (green) and activity (blue) in the first week after my surgery (May 26th). My pre-op pain level of 1 spiked to 8, then ramped down through the next few days.&lt;/p&gt;&lt;a href="https://medium.com/media/2f7e52faf5605070d50646061f6646f5/href"&gt;https://medium.com/media/2f7e52faf5605070d50646061f6646f5/href&lt;/a&gt;&lt;p&gt;As for sleep — I barely slept the two nights after surgery, showed below, but paid back the debt the following day with a whopping 17 hours of sleep. For the rest of the week, I slept from 11–14 hours per night (I average about 7.5 usually), then snoozed a little during the day.&lt;/p&gt;&lt;p&gt;Below shows my sleep as recorded by Autosleep on iOS (right, green), vs pain (left, pink):&lt;/p&gt;&lt;a href="https://medium.com/media/70518a4db24e6a839b0bc502ce9f2cd9/href"&gt;https://medium.com/media/70518a4db24e6a839b0bc502ce9f2cd9/href&lt;/a&gt;&lt;h4&gt;Gut Health&lt;/h4&gt;&lt;p&gt;I won’t go into a lot of detail, but as is common in surgeries involving anesthesia, my gastrointestinal system was… let’s say sleeping… until day four. Hurray for Miralax and fiber!&lt;/p&gt;&lt;h4&gt;Getting In and Out of Bed — “Tea Pot” Method&lt;/h4&gt;&lt;p&gt;The hardest thing to do the first few days was get in and out of bed. An occupational therapist that visited me after my surgery taught me the “Log Roll Method,” to ensure my spine stayed straight during the crucial first few days of healing.&lt;/p&gt;&lt;p&gt;To get into bed, you start by lowering yourself to a sitting position. Then you put your arms up toward the pillow, swing your feet up, bringing you to a side laying position. Then you roll your shoulders, hips, and knees all at the same time onto your back to avoid twisting your back.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/483/1*OFdhDbfjMubL2t-CVziUqg.png" /&gt;&lt;figcaption&gt;(&lt;a href="https://www.saintlukeskc.org/health-library/step-step-using-log-roll-get-bed-hip-care"&gt;source&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;I found that instead of using both arms against the bed surface to support my body, as I was directed and shown above, it was much better to place what would eventually be the “top” arm on my hip (left arm in the diagram), using it to lock my hips and shoulders in the same rotational plane, like a car differential. It makes you look like you’re doing the “this is my handle” part of the “I’m a little teapot” kids dance too as a bonus. I found that this was way less painful getting into bed because I could use the strength of my arm and shoulder to keep my back from twisting instead of my useless low back, abdominal and hip muscles.&lt;/p&gt;&lt;p&gt;Please forgive the photoshop horror, but below is what I mean —the red line is the “tea pot handle” arm that locks your shoulder and hip planes together, preventing a twist:&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/483/1*NG8x2xiEPN4OZc0lqQUU3w.png" /&gt;&lt;figcaption&gt;(&lt;a href="https://www.saintlukeskc.org/health-library/step-step-using-log-roll-get-bed-hip-care"&gt;source&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;h4&gt;Pain management&lt;/h4&gt;&lt;p&gt;I found that taking pain medication on a tight schedule was really important, especially through the night. It’s way better to wake up three times at night to an alarm to take meds for one minute than to wake up in agony because there’s no painkillers in your system. It was also important to write down what I was taking when, as I was in a haze and not able to remember.&lt;/p&gt;&lt;p&gt;By the time I got home, I was on:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;5mg oxycodone, 4hr interval&lt;/li&gt;&lt;li&gt;50mg tramadol, 4hr interval&lt;/li&gt;&lt;li&gt;300mg gabapentin, daily&lt;/li&gt;&lt;li&gt;1000mg tylenol, 8hr interval&lt;/li&gt;&lt;li&gt;15mg cyclobenzoprine, daily&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;and I progressively took less and less of the opiates over time.&lt;/p&gt;&lt;p&gt;I also noticed that pain had a delayed onset. I would be in bed feeling a 4/10 pain and get up to go shower. In the shower I would feel 5/10, and get back in bed at a 5/10. Then the pain would continue to get worse for up to an hour afterwards up to a 7/10. This made it tricky to know when to stop doing something — if I wanted to avoid feeling more than 6/10 pain, I really have to stop when I feel 4/10 pain, and let my body catch up.&lt;/p&gt;&lt;h4&gt;Resting in Bed — Get a wedge pillow!&lt;/h4&gt;&lt;p&gt;For the first few weeks I spent almost all day and night in bed, so getting it set up and comfortable really improved my quality of life.&lt;/p&gt;&lt;p&gt;Getting my legs adjusted so my knees were up just above chest-height to relieve pressure on the low back was really helpful. The bed at the hospital was adjustable so I could get it just right with a remote. Back at home, though, I needed to set up some pillows to get my legs supported to get the pressure relief I needed.&lt;/p&gt;&lt;p&gt;I started with one pillow, added a folded comforter underneath later, then two pillows and blankets eventually landing on a whole big mess to get the shape that helped. I used a rolled-up towel on each side to keep me from rolling while sleeping, as any twisting felt horrible. After a ton of experimentation, I ended up with this:&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/667/1*54Bo6_H7wq3uEl0zOV2X7g.png" /&gt;&lt;/figure&gt;&lt;p&gt;…which is basically the same shape as cheap post-surgery pillows available on Amazon. Looking back I really wish I had bought something like this:&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/426/1*ENsW_JVeaBGcnXuKZnqXRg.png" /&gt;&lt;/figure&gt;&lt;p&gt;One annoyance I dealt with in the first week was not being able to lay on my side (per surgeon’s recommendation), which is how I normally sleep. After a day or two of being on my back all day I was pretty uncomfortable.&lt;/p&gt;&lt;h3&gt;Week Two At Home&lt;/h3&gt;&lt;p&gt;I drastically “turned the corner” two weeks after surgery, reaching the point of feeling generally better than I did before surgery at the two-week mark, though still unable to sit comfortably for more than a few minutes.&lt;/p&gt;&lt;p&gt;I could start to use a laptop in bed for more than a few minutes without it being too uncomfortable.&lt;/p&gt;&lt;p&gt;By the two week mark I was hitting consistent 1/10 or 2/10 pain with painkillers. I did an experiment one day to see how I felt without taking any painkillers at all, but was pretty uncomfortable by the end of the day so I went with just halving everything instead (you can see the mood dip in the below graph).&lt;/p&gt;&lt;p&gt;I saw my surgeon for a follow-up two weeks after surgery and they were happy with how I was recovering. I got prescribed with 8 weeks of PT twice a week and was told I could start sitting up for longer and I could (finally) lay on my side.&lt;/p&gt;&lt;p&gt;The below graph shows my decrease of pain and then rapid increase in activity through the second week.&lt;/p&gt;&lt;a href="https://medium.com/media/1bdd9703629b96490a370f616076885c/href"&gt;https://medium.com/media/1bdd9703629b96490a370f616076885c/href&lt;/a&gt;&lt;h3&gt;Conclusion, 20 Days After Surgery&lt;/h3&gt;&lt;p&gt;As of this writing, 20 days after surgery, I can say that I feel much, much better than I did going into all this, and if I had to do it again I absolutely would.&lt;/p&gt;&lt;p&gt;The first week was a major bummer. However, getting better every day and being able to walk enough to consider it decent exercise I feel much stronger and more human again.&lt;/p&gt;&lt;p&gt;I can now sit comfortably for 1–2 hours, which is already an improvement over pre-surgery. I still can’t bend much (nor should I), so I still need help with a few things around the house that I would normally do (like reaching in the back of a low cabinet, emptying the bottom shelf of the dishwasher, etc).&lt;/p&gt;&lt;p&gt;It’ll be interesting to see how long until I feel comfortable working out, lifting and carrying.&lt;/p&gt;&lt;h3&gt;Conclusion, 2.5 years After Surgery&lt;/h3&gt;&lt;p&gt;As of today, a little more than 2.6 years later (Fall 2023), the surgery was, I’d say, a massive success. I’m back at ~90% of my typical athletic/body ability, and don’t really think about my back much day-to-day.&lt;/p&gt;&lt;p&gt;I am not as flexible in either twisting or bending as I once was, but I’m happy with the tradeoff of little to no pain when sitting, standing, or sleeping. For example, I used to be able to touch the floor under my toes without much issue, but now I can get about down to my ankles. I used to be able to do a total back bend, but now wouldn’t even try it.&lt;/p&gt;&lt;p&gt;I will occasionally have slight flare-ups, where I feel uncomfortable down my leg/calf again, particularly if I lift unsafely, work hard, or sit for many hours in a day for a few days. For example, splitting firewood is fine, but bending down weeding can cause a flare.&lt;/p&gt;&lt;p&gt;But the current flare-ups, which last a day or so, are 1/10 of the pain I used to feel all day, all night, every day, so it’s mostly just mild occasional, much better, annoyance. Sitting on long plane flights is still pretty uncomfortable, but not quite the absolute hell it once was.&lt;br /&gt;I don’t think I’ve had any knock-on effects, such as poor posture, change in gait, etc — my body has adjusted well to the slightly less flexibility.&lt;/p&gt;&lt;img alt="" height="1" src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=fc117fe2f5fe" width="1" /&gt;</description><author>Stories by Zach Wolfe on Medium</author><pubDate>Sat, 30 Sep 2023 06:05:03 GMT</pubDate><guid isPermaLink="true">https://zdwolfe.medium.com/my-experience-with-spinal-fusion-fc117fe2f5fe?source=rss-65d40a7904f------2</guid></item><item><title>Ep. 32 — A culture of experimentation — Lessons learned from creating TwinCam</title><link>https://billylo.medium.com/ep-32-a-culture-of-experimentation-lessons-learned-from-creating-twincam-e55af8bf88da?source=rss-5717c3e67e86------2</link><description>&lt;h3&gt;Ep. 32 — A culture of experimentation — Lessons learned from creating TwinCam&lt;/h3&gt;&lt;p&gt;Intentional practice helps us get better, no matter it’s basketball playing, painting or public speaking. This strategy applies to software engineering too.&lt;/p&gt;&lt;p&gt;If we regularly explore new APIs, toolsets and tinker with them, we would get better at turning ideas into products efficiently.&lt;/p&gt;&lt;p&gt;Experiments are essential and fun…&lt;/p&gt;&lt;p&gt;&lt;a href="https://apps.apple.com/us/app/twincam/id6448645623"&gt;TwinCam&lt;/a&gt; is one of these experiments I ran recently. iOS AV Foundation has opened up and allowed developers to use multiple cameras simultaneously (front, back, wide-angle and depth detection.)&lt;/p&gt;&lt;p&gt;I got curious. 🤔 What if I combine images from two cameras simultaneously? Front camera for recording my face and back camera for the landmark I am trying to capture? Would I be able to fuse them together, &lt;em&gt;in real time?&lt;/em&gt;&lt;/p&gt;&lt;p&gt;There is only one way to find out… Just Do It.&lt;/p&gt;&lt;p&gt;The resulting real-time experience is pretty interesting.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/387/1*XtqzVTHCcI86AQYlFhN58g.jpeg" /&gt;&lt;/figure&gt;&lt;p&gt;It is not too hard to realize the multi-cam features using today’s machine learning toolkits. In this experiment, I used Google’s &lt;a href="https://developers.google.com/ml-kit/vision/selfie-segmentation"&gt;ML Kit — Selfie Segmentation&lt;/a&gt; (so I can try the same idea on Android too.) iOS has a native &lt;a href="https://developer.apple.com/documentation/vision"&gt;Vision Framework&lt;/a&gt; that can do it just as efficiently.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wNHhC2Oz4rvRv8TgE0b0cA.png" /&gt;&lt;/figure&gt;&lt;p&gt;Experiments are fun because you can change direction as you see fit. For TwinCam, the size of the self-image matters a lot; so I added a slider to allow me to adjust things on the fly.&lt;/p&gt;&lt;p&gt;Here are the top 4 highlights when building this:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;You can do a lot of heavy lifting (e.g. object detection, image transforms) on &lt;em&gt;every&lt;/em&gt; preview frame in real time without lags&lt;/strong&gt;, even when you have multiple camera data streams active at the same time on older devices like iPhone X. Modern phones are very powerful.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;You don’t have to learn ML concepts like hyper-parameters tuning to create cool experiences.&lt;/strong&gt; Many of these ARKit or MLKits are user-friendly and require zero ML experience. A sample app is all you need to get going.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Code that are executed in very high frequency requires different debugging techniques. &lt;/strong&gt;Early versions of TwinCam crashed mysteriously in production (SIGSEGV) and they happened on certain devices only. It took me many nights to reproduce the crashes and locate the root cause. I can’t rely on good old print statements because it would generate too much noise and side effects. (Side note: the logic error was in the camera initialization section, not even in the critical&lt;em&gt; &lt;/em&gt;section of the per-frame, high frequency processing logic.)&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Physical device farms like &lt;/strong&gt;&lt;a href="https://www.browserstack.com/test-on-iphone"&gt;&lt;strong&gt;BrowserStack&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt; work really well.&lt;/strong&gt; It has a good free tier that enabled me to explore. It has plugins to things like fastlane to automate your build/deploy/test workflow.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;I hope you find this blog useful. You can download TwinCam for free &lt;a href="https://apps.apple.com/us/app/twincam/id6448645623"&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;A few other experiments if you are curious. The technical learnings are usually captured in my blog (&lt;a href="https://billylo.medium.com"&gt;https://billylo.medium.com&lt;/a&gt;) .&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="https://apps.apple.com/us/app/sidekick-your-personal-driving-assistant/id957252199"&gt;Sidekick&lt;/a&gt;: turns your iPhone into a Dashcam with maps and navigation, speed camera alerts, fuel cost calculations and toll-road optimization.&lt;/li&gt;&lt;li&gt;&lt;a href="https://apps.apple.com/us/app/plane-above-me/id1637351605"&gt;Plane Above Me&lt;/a&gt;: What’s that plane flying above me? A 737 flying to Frankfurt?&lt;/li&gt;&lt;li&gt;&lt;a href="https://apps.apple.com/us/app/how-deep/id6450241637"&gt;How Deep&lt;/a&gt;: shows water depth and altitudes anywhere.&lt;/li&gt;&lt;li&gt;&lt;a href="https://apps.apple.com/us/app/travel-shopping-buddy/id1422456399"&gt;Travel Shopping Buddy&lt;/a&gt;: aim your camera to a price tag and it shows you the tax-inclusive amount in your home currency for easy evaluation&lt;/li&gt;&lt;li&gt;&lt;a href="https://apps.apple.com/us/app/buzzwatch/id6443629077"&gt;BuzzWatch&lt;/a&gt;: To help the hearing impaired, this Apple Watch app listens for important sounds (e.g. car horns) and alert them using vibrations on their wrists promptly. (&lt;a href="https://github.com/billylo1/BuzzWatch"&gt;open-sourced&lt;/a&gt;)&lt;/li&gt;&lt;li&gt;&lt;a href="https://play.google.com/store/apps/details?id=com.evergreenlabs.smartalert"&gt;SmartAlert&lt;/a&gt;: An Android app that works like an alarm monitoring service, except it’s free.&lt;/li&gt;&lt;li&gt;&lt;a href="https://superroute.evergreen-labs.com/"&gt;SuperRoute&lt;/a&gt;: generates route using Google, Apple Maps, MapQuest, HERE, OpenStreetMap, Mapbox, Azure in one click so you can choose the one you like the most.&lt;/li&gt;&lt;li&gt;&lt;a href="https://mycommunitywatch.org"&gt;MyCommunityWatch.org&lt;/a&gt;: notifies you by email if a major crime has been reported in your area (data sourced from Police services)&lt;/li&gt;&lt;li&gt;&lt;a href="https://try-something-new.web.app/"&gt;Try Something New&lt;/a&gt;: Every time a new restaurant opens in your neighbourhood, this web site will email you its details for you to see if it’s worth trying! (powered by Google Maps and Yelp)&lt;/li&gt;&lt;/ul&gt;&lt;img alt="" height="1" src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=e55af8bf88da" width="1" /&gt;</description><author>Stories by Billy Lo on Medium</author><pubDate>Sat, 30 Sep 2023 03:44:43 GMT</pubDate><guid isPermaLink="true">https://billylo.medium.com/ep-32-a-culture-of-experimentation-lessons-learned-from-creating-twincam-e55af8bf88da?source=rss-5717c3e67e86------2</guid></item><item><title>How I keep my shit together</title><link>https://zackproser.com/blog/how-i-keep-my-shit-together</link><description>Practices and protocols that keep me humming along, healthy and productive on a given day</description><author>Zachary Proser</author><pubDate>Sat, 30 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/how-i-keep-my-shit-together</guid></item><item><title>Implementing kind inference</title><link>https://gilmi.me/post/2023/09/30/kind-inference</link><description>A tutorial on how to implement kind inference using unification-based constraint solving.</description><author>λm.me</author><pubDate>Sat, 30 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://gilmi.me/post/2023/09/30/kind-inference</guid></item><item><title>Buy wisely</title><link>https://stephango.com/buy-wisely</link><description>&lt;p&gt;Whenever I buy things I try to prioritize &lt;em&gt;cost per use&lt;/em&gt;. Sometimes I consider other priorities such as &lt;em&gt;cost per smile&lt;/em&gt;, &lt;em&gt;cost per thrill&lt;/em&gt;, &lt;em&gt;cost per externality&lt;/em&gt;, and &lt;em&gt;cost per lesson&lt;/em&gt;.&lt;/p&gt;

&lt;h2 id="cost-per-use"&gt;Cost per use&lt;/h2&gt;

&lt;p&gt;Considering &lt;em&gt;cost per use&lt;/em&gt; helps me make decisions about most non-perishable purchases such as clothes, vehicles, tools, devices, and even services. How much will something cost if I divide its price by its expected number of uses?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cost per use&lt;/em&gt; accounts for longevity. Durable and repairable things may cost more upfront, but over time they cost less than things that break and need to be replaced.&lt;/p&gt;

&lt;p&gt;For example, I buy wool socks from a brand called &lt;a href="https://darntough.com/"&gt;Darn Tough&lt;/a&gt;. They cost about four times more than cheap socks, but they’re durable, comfortable, and have a lifetime guarantee.&lt;/p&gt;

&lt;p&gt;How many times do you wear a pair of socks before holes appear? It may sound silly, but if you amortize the price of each pair of socks over their expected number of uses, a good pair of socks is a worthwhile investment.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Quality&lt;/th&gt;
      &lt;th&gt;Price&lt;/th&gt;
      &lt;th&gt;Uses&lt;/th&gt;
      &lt;th&gt;Cost per use&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Poor&lt;/td&gt;
      &lt;td&gt;$5&lt;/td&gt;
      &lt;td&gt;10&lt;/td&gt;
      &lt;td&gt;$0.50&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Good&lt;/td&gt;
      &lt;td&gt;$10&lt;/td&gt;
      &lt;td&gt;50&lt;/td&gt;
      &lt;td&gt;$0.20&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Best&lt;/td&gt;
      &lt;td&gt;$20&lt;/td&gt;
      &lt;td&gt;200&lt;/td&gt;
      &lt;td&gt;$0.10&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Durable socks save you money. Durable socks save you the hassle of throwing out old socks and buying new ones. Your time has value.&lt;/p&gt;

&lt;p&gt;The best things asymptote to &lt;em&gt;zero dollars per use&lt;/em&gt; over their lifetime. Not all products that have a low &lt;em&gt;cost per use&lt;/em&gt; are expensive. Some things are both affordable and durable.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cost per use&lt;/em&gt; can also be used to measure the value you expect to get out of services like gym memberships, healthcare, or &lt;em&gt;all-you-can-eat&lt;/em&gt; subscriptions like streaming services.&lt;/p&gt;

&lt;h2 id="how-to-assess-durability"&gt;How to assess durability&lt;/h2&gt;

&lt;p&gt;My aim is to have fewer but better things. These are some of the questions I ask of the things that I incorporate into my life:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Will it be as useful to me in the future as it is now?&lt;/li&gt;
  &lt;li&gt;Is it made of durable and maintainable materials?&lt;/li&gt;
  &lt;li&gt;Does it have a timeless &lt;a class="internal-link" href="/style"&gt;style&lt;/a&gt; and aesthetic?&lt;/li&gt;
  &lt;li&gt;Does it age well, wear well, build a &lt;a class="internal-link" href="/scars"&gt;wabi-sabi&lt;/a&gt; patina?&lt;/li&gt;
  &lt;li&gt;Does it retain its resale value? Would someone else want to own it?&lt;/li&gt;
  &lt;li&gt;Can it be disassembled and repaired?&lt;/li&gt;
  &lt;li&gt;Does it have replaceable, non-proprietary parts that are easy to acquire?&lt;/li&gt;
  &lt;li&gt;Can it be powered with a standard plug or replaceable batteries?&lt;/li&gt;
  &lt;li&gt;Can it be modified and upgraded?&lt;/li&gt;
  &lt;li&gt;Has the maker existed for at least as long as I hope to keep the product?&lt;/li&gt;
  &lt;li&gt;Can it perform many jobs, or only one?&lt;/li&gt;
  &lt;li&gt;Does it have a guarantee?&lt;/li&gt;
  &lt;li&gt;Does it rely on other products or technologies that aren’t durable?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not all questions are relevant to all things. Not all things need to satisfy every requirement. However, I feel better about buying things that check many of these boxes. A movement called “buy it for life” has sprung up around similar priorities.&lt;/p&gt;

&lt;h2 id="how-to-estimate-uses"&gt;How to estimate uses&lt;/h2&gt;

&lt;p&gt;To make purchasing decisions based on &lt;em&gt;cost per use&lt;/em&gt;, you should try to guess how many times you will use the product. This sounds obvious, but it can be hard to determine, especially if it’s something you’ve never bought before.&lt;/p&gt;

&lt;p&gt;If you are new to a hobby, like cooking or skiing, you may not yet know how many times you will use a piece of gear. It’s easy to significantly overestimate or underestimate how many times you will use something.&lt;/p&gt;

&lt;p&gt;You should, however, be able to guess the frequency of use. Is this something you might use once per hour, day, week, month, year? This will help you determine your budget. How many dollars per month do you want to spend on this?&lt;/p&gt;

&lt;p&gt;If you don’t know the frequency yet consider borrowing, renting, or otherwise trialing. Remember the ideal &lt;em&gt;cost per use&lt;/em&gt; trends to $0. The most cost-effective choice is to not buy something you don’t need.&lt;/p&gt;

&lt;p&gt;There is an opportunity cost to every dollar. A dollar you spend on a something that won’t get much use is a dollar you can’t save for something that will.&lt;/p&gt;

&lt;p&gt;If you’re only going to use something once, then &lt;em&gt;cost per use&lt;/em&gt; may not be the right heuristic for this decision. If you have disposable income, then the &lt;em&gt;cost per use&lt;/em&gt; lens should nonetheless guide you towards something with good resale value.&lt;/p&gt;

&lt;p&gt;The best things to splurge on are the things you use the most. If you sit on a chair eight hours a day, then a nice chair is probably a good investment.&lt;/p&gt;

&lt;h2 id="reducing-the-cost-side"&gt;Reducing the cost side&lt;/h2&gt;

&lt;p&gt;Durable things reduce &lt;em&gt;cost per use&lt;/em&gt; by giving you more uses before they break. But you can also reduce &lt;em&gt;cost per use&lt;/em&gt; by acquiring products at a discount. You may be able to buy second-hand, inherit, or barter.&lt;/p&gt;

&lt;p&gt;Durable things can often be great second-hand purchases &lt;em&gt;because&lt;/em&gt; they are durable, and may not be the current fashion.&lt;/p&gt;

&lt;h2 id="alternative-heuristics"&gt;Alternative heuristics&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Cost per use&lt;/em&gt; is not the only way to make buying decisions. For things like consumables and experiences, you may need to approach the decision differently.&lt;/p&gt;

&lt;h3 id="cost-per-smile"&gt;Cost per smile&lt;/h3&gt;

&lt;p&gt;How much joy can you get out of each dollar? Some things bring lots of joy for a small amount of money: ice cream, sunsets, nature walks. A cheap date can bring many smiles per dollar.&lt;/p&gt;

&lt;p&gt;Some things have excellent &lt;em&gt;cost per use&lt;/em&gt; but few &lt;em&gt;smiles per use&lt;/em&gt;. I don’t smile every time I put my nice socks on. A few years ago I bought a fairly expensive electric road bike. It probably didn’t have the best &lt;em&gt;cost per use&lt;/em&gt;, but it gives me the most &lt;em&gt;joy per use&lt;/em&gt; of any product I purchased in recent memory. It’s worth it to me.&lt;/p&gt;

&lt;h3 id="cost-per-thrill"&gt;Cost per thrill&lt;/h3&gt;

&lt;p&gt;Some experiences have high &lt;em&gt;intensity per dollar&lt;/em&gt;. A potency, concentration, or strength of experience. For example, foods can have high &lt;em&gt;flavor per dollar&lt;/em&gt; — like hot sauce, pickles, curry, garlic, or mustard. Roller coasters, bungee jumping, or skydiving may give you a &lt;em&gt;thrill per dollar&lt;/em&gt; that you can’t beat.&lt;/p&gt;

&lt;h3 id="cost-per-externality"&gt;Cost per externality&lt;/h3&gt;

&lt;p&gt;Some purchases may have externalities that are incompatible with your values. For example, you may wish to buy organic produce, pasture-raised eggs, or fair trade coffee. You may wish to buy products that preserve privacy, reduce environmental impact, or support a cause you believe in.&lt;/p&gt;

&lt;p&gt;You can choose to incorporate externalities into the cost that you are paying instead of letting that cost be borne by others, or by society at large. This may increase your personal &lt;em&gt;cost per use&lt;/em&gt;, but it might help change how society values things, or simply make you feel good.&lt;/p&gt;

&lt;h3 id="cost-per-lesson"&gt;Cost per lesson&lt;/h3&gt;

&lt;p&gt;Knowledge has a cost. Sometimes that cost is monetary, sometimes that cost is time, sometimes that cost is &lt;a class="internal-link" href="/pain"&gt;pain&lt;/a&gt;. Sometimes it’s all three.&lt;/p&gt;

&lt;p&gt;You might learn more about investing by purchasing a handful of shares in public companies than going to graduate school. You might learn more about making movies by trying to film one on your phone than going to film school.&lt;/p&gt;

&lt;p&gt;Acquiring new ideas and perspectives can have a worthwhile cost. Being an early adopter of a product often has a poor &lt;em&gt;cost per use&lt;/em&gt;, but its value is in the learning and experience per dollar.&lt;/p&gt;</description><author>Steph Ango</author><pubDate>Sat, 30 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://stephango.com/buy-wisely</guid></item><item><title>My Kind of Weather</title><link>https://foreverliketh.is/blog/my-kind-of-weather/</link><description>&lt;details&gt;
Image Details &amp; Source
&lt;p&gt;&lt;a href="https://unsplash.com/photos/awYEQyYdHVE"&gt;a close up of a planet with a black background&lt;/a&gt; by &lt;a href="https://unsplash.com/@planetvolumes"&gt;Planet Volumes&lt;/a&gt; on &lt;a href="https://unsplash.com/"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It is a &lt;a href="https://en.wikipedia.org/wiki/3D_rendering"&gt;3D render&lt;/a&gt; of &lt;a href="https://en.wikipedia.org/wiki/Jupiter"&gt;Jupiter&lt;/a&gt;; and I mean to highlight its &lt;a href="https://en.wikipedia.org/wiki/Great_Red_Spot"&gt;Great Red Spot&lt;/a&gt;, the largest &lt;a href="https://en.wikipedia.org/wiki/Storm"&gt;storm&lt;/a&gt; in our &lt;a href="https://en.wikipedia.org/wiki/Solar_System"&gt;Solar System&lt;/a&gt;.&lt;/p&gt;
&lt;/details&gt;
&lt;hr /&gt;
&lt;p&gt;This page was last modified: &lt;strong&gt;October 4th, 2023&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h5 id="diamond-in-the-rough"&gt;Diamond in the Rough &lt;a class="anchor" href="#diamond-in-the-rough"&gt;#&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;There is an event commonly known as the &lt;a href="https://en.wikipedia.org/wiki/100-year_flood"&gt;Hundred Year Flood&lt;/a&gt;. As its name implies, and in terms of the &lt;a href="https://en.wikipedia.org/wiki/Life_expectancy"&gt;human lifespan&lt;/a&gt;, it is &lt;em&gt;&lt;strong&gt;quite&lt;/strong&gt;&lt;/em&gt; rare. It is possible for you to live a long, fulfilling life without ever experiencing one, traveler. &lt;a href="https://en.wikipedia.org/wiki/Parcae"&gt;The Parcae&lt;/a&gt; had other plans for me, however; &lt;strong&gt;and my gratitude to them will persist until the day I die.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;While I do not mean to celebrate the harm, deaths, tragedies, etc. that befall many who experience such events, their occurrence does contribute to the beauty that can be found. The &lt;a href="https://en.wikipedia.org/wiki/Silver_lining_(idiom)"&gt;silver lining&lt;/a&gt;, so to speak.&lt;/p&gt;
&lt;p&gt;For how else could &lt;a href="https://en.wikipedia.org/wiki/Terra_(mythology)"&gt;Terra&lt;/a&gt; humble our hubris than through a small, yet awe-inspiring, display of Her power? How else could She restore a reverence more lost than ever before in human history? To interpret her demonstration solely as a misfortune is a disservice to the gift that it delivers&amp;hellip; A reminder of what is &lt;em&gt;&lt;strong&gt;truly&lt;/strong&gt;&lt;/em&gt; important.&lt;/p&gt;
&lt;p&gt;A clarity potent enough to make neighbors put aside their petty squabbles: &amp;ldquo;Why won&amp;rsquo;t your dogs shut up?!&amp;rdquo;, &amp;ldquo;Your tree keeps shedding leaves in MY POOL!&amp;rdquo;, &amp;ldquo;The division between our properties is HERE, NOT THERE!&amp;rdquo;&lt;/p&gt;
&lt;p&gt;A cleansing intense enough to sever the Wired and break the masses free of its digital illusion. So free, that the pity is on the &amp;ldquo;fortunate&amp;rdquo; who managed to stay &amp;ldquo;connected&amp;rdquo;, because they cannot even fathom the prison they&amp;rsquo;re in, let alone being released from it.&lt;/p&gt;
&lt;p&gt;I knew an old couple that sat many evenings out on their porch. Sometimes both, sometimes one. They&amp;rsquo;d sit longer into the night than seemed reasonable, as if they were waiting for something that never came. It wasn&amp;rsquo;t until after the disaster that I believed I understood why.&lt;/p&gt;
&lt;p&gt;Every subsequent evening, almost the entire street went, and sat, and talked, and shared, and played, &lt;em&gt;&lt;strong&gt;for hours&lt;/strong&gt;&lt;/em&gt;, around &lt;em&gt;&lt;strong&gt;THEIR porch&lt;/strong&gt;&lt;/em&gt;. Stubborn habit? Or had they just never forgotten a normal that everyone else briefly remembered?&lt;/p&gt;
&lt;p&gt;And I do mean &lt;em&gt;&lt;strong&gt;briefly&lt;/strong&gt;&lt;/em&gt;, visitor. The one in a hundred is enough to provide &lt;em&gt;&lt;strong&gt;a glimpse&lt;/strong&gt;&lt;/em&gt; of days past, but it isn&amp;rsquo;t enough to bring them back. You&amp;rsquo;d have to dial it up, and even then I&amp;rsquo;m not so sure&amp;hellip; Some say we&amp;rsquo;ve &lt;a href="https://en.wikipedia.org/wiki/Crossing_the_Rubicon"&gt;crossed the Rubicon&lt;/a&gt;. It&amp;rsquo;s a risky game too, after a certain point the odds surely take more than could be gained.&lt;/p&gt;
&lt;p&gt;Regardless, the hardship, struggle, anxiety, camaraderie, compassion, connection, tenacity, simplicity I experienced, all of it, was unforgettable.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve no doubt &lt;a href="https://en.wikipedia.org/wiki/Jupiter_(mythology)"&gt;Jove&lt;/a&gt; has plenty of storms in store for all of us in one way or another. But here, at last, I understand a strange truth&amp;hellip;&lt;/p&gt;
&lt;p&gt;My kind of weather must be weathered.&lt;/p&gt;
&lt;p&gt;May we receive more from it than we lose, dear reader.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Contributors: &lt;a href="https://en.wikipedia.org/wiki/Ancient_Rome"&gt;Ancient Rome&lt;/a&gt; &amp;amp; &lt;a href="https://en.wikipedia.org/wiki/Serial_Experiments_Lain"&gt;Serial Experiments Lain&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://indieweb.org/syndication-models"&gt;Syndication&lt;/a&gt;: &lt;a href="https://indieweb.social/@accordionpolar/111155888442009784"&gt;Mastodon&lt;/a&gt; &amp;amp; &lt;a href="https://forum.agoraroad.com/index.php?threads/agora-road-travelouge-september-blogging.6061/post-88439"&gt;Agora Road&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This blog post is part of &lt;a href="https://indieweb.org/indieweb-carnival"&gt;IndieWeb Carnival&lt;/a&gt;. A project trying to help people post more on their personal websites. This month&amp;rsquo;s host is &lt;a href="https://www.jeremycherfas.net/"&gt;Jeremy Cherfas&lt;/a&gt; and their topic was &amp;lsquo;My Kind of Weather&amp;rsquo; [&lt;a href="https://www.jeremycherfas.net/blog/my-kind-of-weather"&gt;#1&lt;/a&gt; &amp;amp; &lt;a href="https://www.jeremycherfas.net/blog/delightful-weathers"&gt;#2&lt;/a&gt;]&lt;/p&gt;
&lt;p&gt;This blog post is also part of Agora Road Travelogue - Sep [&lt;a href="https://forum.agoraroad.com/index.php?threads/agora-road-travelouge-september-blogging.6061/"&gt;#1&lt;/a&gt; &amp;amp; #2&lt;!--todo--&gt;]. A project attempting to encourage individuals to both write and exercise ownership over their content.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id="comments-section"&gt;Comments Section &lt;a class="anchor" href="#comments-section"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Email: &lt;a href="mailto:hi@foreverliketh.is"&gt;hi@foreverliketh.is&lt;/a&gt;&lt;/p&gt;

&lt;div id="fastcomments-widget"&gt;&lt;/div&gt;

&lt;details&gt;
IndieWeb h-entry
&lt;article class="h-entry"&gt;
&lt;h1 class="p-name"&gt;My Kind of Weather&lt;/h1&gt;
&lt;p&gt;Published by &lt;a class="p-author h-card" href="https://foreverliketh.is/"&gt;foreverliketh.is&lt;/a&gt;
on &lt;time class="dt-published" datetime="2023-09-30 10:00:00"&gt;30&lt;sup&gt;th&lt;/sup&gt; September 2023&lt;/time&gt;&lt;/p&gt;
&lt;p class="p-summary"&gt;Be Not Afraid&lt;/p&gt;
&lt;div class="e-content"&gt;
&lt;p&gt;Diamond in the Rough&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;a class="u-url" href="https://foreverliketh.is/blog/my-kind-of-weather/"&gt;My Kind of Weather&lt;/a&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;a class="u-syndication" href="https://forum.agoraroad.com/index.php?threads/agora-road-travelouge-september-blogging.6061/post-88439" rel="syndication"&gt;Agora Road&lt;/a&gt; &amp;
&lt;a class="u-syndication" href="https://indieweb.social/@accordionpolar/111155888442009784" rel="syndication"&gt;Mastodon&lt;/a&gt;
&lt;/div&gt;
&lt;a class="p-category" href="https://foreverliketh.is/categories/psychology/"&gt;Psychology&lt;/a&gt;
&lt;/article&gt;
&lt;/details&gt;</description><author>foreverliketh.is</author><pubDate>Sat, 30 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://foreverliketh.is/blog/my-kind-of-weather/</guid></item><item><title>Why Pocket is Rolling with Rollkit</title><link>https://olshansky.info/posts/why-pocket-is-rolling-with-rollkit/</link><description>A historical and technical deep dive leading to the next steps in the Pocket Network Protocol</description><author>🦉 olshansky 🦁</author><pubDate>Sat, 30 Sep 2023 02:39:57 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/posts/why-pocket-is-rolling-with-rollkit/</guid></item><item><title>Introducing Shinobi Art Engine</title><link>https://thoughts.greyh.at/posts/shinobi/</link><description>Today I am thrilled to introduce the Shinobi Art Engine, the ultimate tool for creating NFT collections. This is an extension of the amazing work done by the HashLips team, known for their pioneering efforts in NFT art generation, and it&amp;rsquo;s the first tool to support both BCH and ETH NFT collections. The CashNinjas team has been working on this project for a while now and we are excited to share it with the world.</description><author>Terminal Thoughts</author><pubDate>Sat, 30 Sep 2023 02:25:39 GMT</pubDate><guid isPermaLink="true">https://thoughts.greyh.at/posts/shinobi/</guid></item><item><title>Drift Vector Playtest &amp;amp;amp; High Score Competition!</title><link>/blog/2023/09/30/drift-vector-playtest-and-high-score-contest.html</link><description>Aaaaagghhh! In the manic rush to get a demo build out in time for the Steam SHMUP-Fest and edit a trailer and do various other bits of social media promotion &amp;amp; so forth I totally neglected this blog or let my dear readers know that I’m currently running a playtest &amp;amp; high-score contest for Drift Vector &amp;amp; throwing in a little $50 Steam credit for the winner: Well, it’s not toooo late! By my calculation, at the time of writing there’s still a little over 52 hours left to participate!</description><author>biggles.games</author><pubDate>Sat, 30 Sep 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">/blog/2023/09/30/drift-vector-playtest-and-high-score-contest.html</guid></item><item><title>Circles interactive improved</title><link>http://localhost:4000/2023/09/30/circles-interactive-improved.html</link><description>Circles interactive improved</description><author>ms75</author><pubDate>Sat, 30 Sep 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">http://localhost:4000/2023/09/30/circles-interactive-improved.html</guid></item><item><title>Builder Censorship:  ethereum's rotten core</title><link>https://mteam.space/posts/builder-censorship--ethereums-rotten-core/</link><description>The problem of builder censorship in Ethereum and how to solve it with inclusion lists.</description><author>mteam's Writings</author><pubDate>Fri, 29 Sep 2023 23:52:10 GMT</pubDate><guid isPermaLink="true">https://mteam.space/posts/builder-censorship--ethereums-rotten-core/</guid></item><item><title>GDPR, gamified &amp;amp; a serious DX issue</title><link>https://holzer.online/weeknotes/2023/39/index.html</link><description>&lt;time datetime="2023-09-29 20:35"&gt;&lt;/time&gt;

&lt;p&gt;
Since this week my parental part-time is over, I'm back to working my standard 40 hours. With so much of my time to available to the company again, sure enough somebody deemed it a good idea to assign a training course on GDPR to me. Ok, in all fairness, that course was probably assigned to just about everybody in the department, in order to tick a box on some spreadsheet, for you know, a bit of corporate cover your ass can go a long way sometimes, but I'm digressing... Anyway as a good compliant employee, I started the course, hoping for the best. It turned out to be gamified, in - I kid you not - the form of detective's story and the look and feel and a UX of a badly programmed game, that reminded me very much on the numerous hobbyist game from the hey-days of Flash...
&lt;/p&gt;

&lt;p&gt;
But on to a more serious matter of user and developer experience. My son is currently digging a bit into programming with Scratch on the Raspberry Pi. Following along a tutorial, after making the figure on the screen do some funny things, tweaking and exploring here and there, he got to the point where he had a little game that he wanted to spice up by checking for a game over condition.
&lt;/p&gt;

&lt;p&gt;
So, a quick test: the player has lost their last life, tell me, why is the if branch not executed?
&lt;/p&gt;

&lt;img src="/weeknotes/2023/39/scratch-literal.png" /&gt;

&lt;p&gt;
The logic looks quite sound, doesn't it? Perhaps a different notation helps to spot the problem faster:
&lt;/p&gt;

&lt;code&gt;&lt;pre&gt;
lives--;
if ("lives" &amp;lt; 1) {
    console.log("Game over");
}&lt;/pre&gt;&lt;/code&gt;

&lt;p&gt;
So, what happened? While following the tutorial he didn't drag the variable as a block, but typed it out into the field, which makes it a string literal (a legitimate thing to compare something with). Referencing the actual variable looks like that (not quite an optical difference like night and day):
&lt;/p&gt;

&lt;img src="/weeknotes/2023/39/scratch-variable.png" /&gt;

&lt;p&gt;
As block-based programming languages have found their way into school curricula as well, I was wondering how many kids' enthusiasm for computing might get curbed by such little matters of affordances that cause confusion or frustration.
&lt;/p&gt;

&lt;p&gt;
Last, but not least, I added two items to the site: a client-side &lt;a href="/search/"&gt;full-text search&lt;/a&gt; (powered by &lt;a href="https://lunrjs.com"&gt;lunr.js&lt;/a&gt;) and a &lt;a href="/colophon/"&gt;Colophon&lt;/a&gt;, because I always like to read about the website setup of other people for inspiration and also to give credit where credit is due to a few libraries I use, which were created by people who generously open-sourced them.
&lt;/p&gt;</description><author>Fabian's public notepad</author><pubDate>Fri, 29 Sep 2023 21:35:00 GMT</pubDate><guid isPermaLink="true">https://holzer.online/weeknotes/2023/39/index.html</guid></item><item><title>Racket Beyond Languages</title><link>https://nikhilism.com/post/2023/racket-beyond-languages/</link><description>Chris Krycho, a person I really respect, is learning Racket to build programming languages. Racket is generally slotted as a language to build languages. The popular books focus on Racket innovations related to constructing Domain Specific Languages. This include hygienic macros, the Racket loading and evaluation phases and the module system. While it is uniquely suited for creating languages 1, Racket is also a research vehicle for a large body of programming language research.</description><author>Nikhil's blog</author><pubDate>Fri, 29 Sep 2023 18:45:43 GMT</pubDate><guid isPermaLink="true">https://nikhilism.com/post/2023/racket-beyond-languages/</guid></item><item><title>Dark Mode for the PET 2001 and Themed Range Sliders</title><link>https://www.masswerk.at/nowgobang/2023/pet-dark-mode-and-slider-styling</link><description>Also, how to style range inputs using CSS, including accent colors.</description><author>mass:werk – Now Go Bang!</author><pubDate>Fri, 29 Sep 2023 16:00:00 GMT</pubDate><guid isPermaLink="true">https://www.masswerk.at/nowgobang/2023/pet-dark-mode-and-slider-styling</guid></item><item><title>Suggested readings week 39/2023</title><link>https://www.stefanocosta.org/suggested-readings-week-39-2023/</link><description>&lt;hr /&gt;&lt;h3 id="self-supervised-learning-the-dark-matter-of-intelligence"&gt;&lt;a href="https://ai.meta.com/blog/self-supervised-learning-the-dark-matter-of-intelligence/?ref=stefanocosta.org" rel="nofollow noreferrer noopener"&gt;Self-supervised learning: The dark matter of intelligence&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Humans rely on their previously acquired background knowledge of how the world works, and build new knowledge based on experience and simula.&lt;/p&gt;&lt;p&gt;How do we get machines to do the same? Self Supervised Learning is one step in this direction. Instead of&lt;/p&gt;</description><author>Food for Thought (by Stefano Costa)</author><pubDate>Fri, 29 Sep 2023 15:30:58 GMT</pubDate><guid isPermaLink="true">https://www.stefanocosta.org/suggested-readings-week-39-2023/</guid></item><item><title>Converting H1 to Inline Notes for Evergreen Notes</title><link>http://evantravers.com/articles/2023/09/29/converting-h1-to-inline-notes-for-evergreen-notes/</link><description>&lt;p&gt;My friend Joschua wrote recently about the &lt;a href="https://joschua.io/posts/2023/09/16/three-tips-for-better-obsidian-notes/#2-enable-inline-titles"&gt;virtues of Inline Titles in note-making&lt;/a&gt;. I've been solidly on the &amp;quot;Title &amp;amp; H1&amp;quot; camp for a while, but I've also been manually keeping them synced.&lt;/p&gt;
&lt;p&gt;I was asking Joschua what he did to migrate his notes over to inline titles… and he replied he's been manually changing them as he encounters it in his notes. So I wrote him a script:&lt;/p&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-ruby" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;# work through every file ending in .md&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;Dir&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;glob&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;./**/*.md&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;each&lt;/span&gt; &lt;span&gt;do&lt;/span&gt; &lt;span&gt;|&lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;# extract the left part of the filename&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;filename&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;File&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;basename&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;'&lt;/span&gt;&lt;span&gt;.md&lt;/span&gt;&lt;span&gt;'&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;# read the file&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;content&lt;/span&gt;  &lt;span&gt;=&lt;/span&gt; &lt;span&gt;File&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;read&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;# split the file into an array of strings&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;lines&lt;/span&gt;    &lt;span&gt;=&lt;/span&gt; &lt;span&gt;content&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;lines&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;# find a line that matches the filename&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;matching_title&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;lines&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;find_index&lt;/span&gt; &lt;span&gt;&amp;amp;lbrace&lt;/span&gt; &lt;span&gt;|&lt;/span&gt;&lt;span&gt;l&lt;/span&gt;&lt;span&gt;|&lt;/span&gt; &lt;span&gt;l&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;match&lt;/span&gt; &lt;span&gt;/&lt;/span&gt;&lt;span&gt;^&lt;/span&gt;&lt;span&gt;# &lt;/span&gt;&lt;span&gt;#&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;filename&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;\s&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;/&lt;/span&gt; &lt;span&gt;&amp;amp;rbrace&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;if&lt;/span&gt; &lt;span&gt;matching_title&lt;/span&gt; &lt;span&gt;then&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;# find block of empty lines&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;blank_lines&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;matching_title&lt;/span&gt; &lt;span&gt;+&lt;/span&gt; &lt;span&gt;1&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;while&lt;/span&gt; &lt;span&gt;lines&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;blank_lines&lt;/span&gt;&lt;span&gt;]&lt;/span&gt; &lt;span&gt;and&lt;/span&gt; &lt;span&gt;lines&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;blank_lines&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;^&lt;/span&gt;&lt;span&gt;\s&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;blank_lines&lt;/span&gt; &lt;span&gt;+=&lt;/span&gt; &lt;span&gt;1&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;# remove the header matching the title and following blank lines&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;lines&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;slice!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;matching_title&lt;/span&gt;&lt;span&gt;..&lt;/span&gt;&lt;span&gt;blank_lines&lt;/span&gt; &lt;span&gt;-&lt;/span&gt; &lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;File&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;write&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;lines&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(from &lt;a href="https://gist.github.com/evantravers/dbe040f37c3b10fcf82f5296435a2fa1"&gt;headers.rb&lt;/a&gt;)&lt;/p&gt;
&lt;div class="admonition warning"&gt;
&lt;p&gt;This script is destructive and changes your files without warning!&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;I would counsel only running it on a backed-up folder if you are comfortable with scripts.&lt;/p&gt;
&lt;p&gt;If this transformation is something you want and you need help, you can &lt;a href="/contact"&gt;message me&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If there was enough interest, I'm sure the basic logic could be translated to JavaScript and turned into a plugin for folks that want to embrace inline titles in their Obsidian notes.&lt;/p&gt;</description><author>trv.rs</author><pubDate>Fri, 29 Sep 2023 13:26:00 GMT</pubDate><guid isPermaLink="true">http://evantravers.com/articles/2023/09/29/converting-h1-to-inline-notes-for-evergreen-notes/</guid></item><item><title>A Bit of SaaS Weekly: Onboarding</title><link>https://ethanmick.com/a-bit-of-saas-weekly-onboarding/</link><description>&lt;p&gt;This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by &lt;a href="https://ethanmick.com/"&gt;Ethan Mick&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Starting anything new is both exhilarating and daunting. There is so much promise and uncertainty, a tantalizing promise of what might be and if you will be able to achieve&lt;/p&gt;</description><author>Build SaaS with Ethan</author><pubDate>Fri, 29 Sep 2023 13:00:29 GMT</pubDate><guid isPermaLink="true">https://ethanmick.com/a-bit-of-saas-weekly-onboarding/</guid></item><item><title>Thinking in trees and lines, formatting Rust</title><link>https://dystroy.org/blog/fmt-rust-trees-lines/</link><description>&lt;p&gt;I see my code projects in trees and lines, and it affects how I want it to be formatted.&lt;/p&gt;</description><author>dystroy|Canop / blog</author><pubDate>Fri, 29 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://dystroy.org/blog/fmt-rust-trees-lines/</guid></item><item><title>Circles interactive</title><link>http://localhost:4000/2023/09/29/circles-interactive.html</link><description>Circles interactive</description><author>ms75</author><pubDate>Fri, 29 Sep 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">http://localhost:4000/2023/09/29/circles-interactive.html</guid></item><item><title>Wordcount III: Beating the system `wc`</title><link>https://bytepawn.com/beating-the-system-wc.html</link><description>&lt;p&gt;In this follow-up to the previous article about writing a C++ version of the Unix command-line utility &lt;code&gt;wc&lt;/code&gt;, I make some modifications to beat my system &lt;code&gt;wc&lt;/code&gt; in performance tests.&lt;br /&gt;&lt;br /&gt; &lt;img alt="wc" src="/images/wcpp3.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Fri, 29 Sep 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/beating-the-system-wc.html</guid></item><item><title>kitty, Starship and Terminal Customization</title><link>https://thoughts.greyh.at/posts/starship/</link><description>As someone who spends a significant chunk of time immersed in terminal windows, constantly tweaking and optimizing, I thought I had achieved an ideal setup with zsh and oh-my-zsh. However, in the spirit of continuous improvement, I recently embarked on a quest for re-evaluation and potential enhancement.</description><author>Terminal Thoughts</author><pubDate>Thu, 28 Sep 2023 05:31:39 GMT</pubDate><guid isPermaLink="true">https://thoughts.greyh.at/posts/starship/</guid></item><item><title>Subtraction Is Functionally Complete</title><link>https://orlp.net/blog/subtraction-is-functionally-complete/</link><description>&lt;p&gt;To be precise, &lt;a href="https://en.wikipedia.org/wiki/IEEE_754"&gt;IEEE-754&lt;/a&gt; floating point
subtraction is &lt;a href="https://en.wikipedia.org/wiki/Functional_completeness"&gt;functionally
complete&lt;/a&gt;. That means you
can construct any binary circuit using nothing but floating point subtraction.&lt;/p&gt;
&lt;p&gt;To see how, we must start at the bottom. I quote the IEEE 754-2019 standard, section 6.3:&lt;/p&gt;
&lt;blockquote&gt;
&lt;h3 id="6-3-the-sign-bit"&gt;&lt;a class="anchor" href="#6-3-the-sign-bit"&gt;6.3 The sign bit&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;[…] When neither the inputs nor result are NaN, […]; the sign of a sum, or of a difference $x−y$
regarded as a sum $x+(−y)$, differs from at most one of the addends’ signs; […].
These rules shall apply even when operands or results are zero or infinite.&lt;/p&gt;
&lt;p&gt;When the sum of two operands with opposite signs (or the difference of two operands with like
signs) is exactly zero, the sign of that sum (or difference) shall be $+0$ under all rounding-direction
attributes except &lt;code&gt;roundTowardNegative&lt;/code&gt;; under that attribute, the sign of an exact zero sum (or
difference) shall be $−0$.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let’s dissect that.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A subtraction $x - y$ is considered a sum $x + (-y)$.&lt;/li&gt;
&lt;li&gt;Zero can have a sign, $-0$ and $0$ are distinct entities (although they compare
equal when testing with &lt;code&gt;==&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;If both of the addends have the same sign, the output must have that sign.
However, for $x - y$ that means if $x$ and $y$ have &lt;em&gt;different&lt;/em&gt; signs the output
must have the sign of $x$.&lt;/li&gt;
&lt;li&gt;If $x$ and $y$ have the same sign, and $x - y$ is zero, the output will be
$+0$ for all rounding modes except &lt;code&gt;roundTowardNegative&lt;/code&gt;, then it will be $-0$.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now since the default rounding mode in virtually every context is &lt;code&gt;roundTiesToEven&lt;/code&gt;,
we shall assume that from now on. However, everything works analogously even for
&lt;code&gt;roundTowardNegative&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="a-truth-table"&gt;&lt;a class="anchor" href="#a-truth-table"&gt;A truth table&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So, what does that give us when subtracting zeroes?&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-python"&gt;&lt;span&gt;-&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0 &lt;/span&gt;&lt;span&gt;- -&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0 &lt;/span&gt;&lt;span&gt;= +&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0  &lt;/span&gt;&lt;span style="color: #76647b;"&gt;# Same sign, must be +0.
&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0 &lt;/span&gt;&lt;span&gt;- +&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0 &lt;/span&gt;&lt;span&gt;= -&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0  &lt;/span&gt;&lt;span style="color: #76647b;"&gt;# Different signs, sign from first argument.
&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0 &lt;/span&gt;&lt;span&gt;- -&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0 &lt;/span&gt;&lt;span&gt;= +&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0  &lt;/span&gt;&lt;span style="color: #76647b;"&gt;# Different signs, sign from first argument.
&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0 &lt;/span&gt;&lt;span&gt;- +&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0 &lt;/span&gt;&lt;span&gt;= +&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0  &lt;/span&gt;&lt;span style="color: #76647b;"&gt;# Same sign, must be +0.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Interesting… What if we say that $-0$ is false and $+0$ is true?&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-python"&gt;&lt;span&gt;A B | O
&lt;/span&gt;&lt;span&gt;----+--
&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0 0 &lt;/span&gt;&lt;span&gt;| &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1
&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0 1 &lt;/span&gt;&lt;span&gt;| &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0
&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1 0 &lt;/span&gt;&lt;span&gt;| &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1
&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1 1 &lt;/span&gt;&lt;span&gt;| &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our resulting truth table is equivalent to ${A \vee \neg B}$, or ${B \to A}$ (also known as the
&lt;a href="https://en.wikipedia.org/wiki/IMPLY_gate"&gt;IMPLY&lt;/a&gt; gate, albeit with the arguments swapped). It turns
out this truth table is &lt;a href="https://en.wikipedia.org/wiki/Functional_completeness"&gt;functionally
complete&lt;/a&gt;, which means we can make arbitrary
circuits using only this gate.
Technically speaking, it is only functionally complete if given access to the
constant false. This is necessary to produce a NOT gate, and NOT + IMPLY is
a functionally complete set. I don’t know a better term for ‘functionally complete
if given access to some constant value’, however.&lt;/p&gt;
&lt;aside&gt;
NAND and NOR are truly functionally complete by
themselves, even without access to any particular constant value. This is very
valuable when constructing microchips as you only need to be able to produce a
single kind of component, and do not need to worry about routing a consistent
low signal anywhere to produce a NOT gate.
&lt;/aside&gt;
&lt;h2 id="subtraction-circuits"&gt;&lt;a class="anchor" href="#subtraction-circuits"&gt;Subtraction circuits&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let’s build a demo in Python. First we’ll define our constants and allow us to print them nicely.
Note that even though they are distinct entities, $+0$ and $-0$ compare equal in IEEE 754 floating
point, so we must first extract the sign before comparing to 0 to distinguish.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-python"&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;import &lt;/span&gt;&lt;span&gt;math
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;f_false = -&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0.0
&lt;/span&gt;&lt;span&gt;f_true = &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0.0
&lt;/span&gt;&lt;span&gt;f_repr = &lt;/span&gt;&lt;span style="color: #215da8;"&gt;lambda &lt;/span&gt;&lt;span&gt;x: &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;True &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;if &lt;/span&gt;&lt;span&gt;math.copysign(&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1&lt;/span&gt;&lt;span&gt;, x) &amp;gt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0 &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;else &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;False
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can now make a NOT gate by using the fact that $-0 - x$ flips the sign of
zero $x$:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-python"&gt;&lt;span&gt;f_not = &lt;/span&gt;&lt;span style="color: #215da8;"&gt;lambda &lt;/span&gt;&lt;span&gt;x: f_false - x
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s test that:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-python"&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; f_repr(f_not(f_false))
&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;True
&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; f_repr(f_not(f_true))
&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;False
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Great! We can also build an OR gate by noticing that if we flip the sign of
the second argument before subtracting, we always get $+0$ (true) unless
both arguments are $-0$ (false):&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-python"&gt;&lt;span&gt;f_or = &lt;/span&gt;&lt;span style="color: #215da8;"&gt;lambda &lt;/span&gt;&lt;span&gt;a, b: a - f_not(b)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s test it out:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-python"&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; f_repr(f_or(f_false, f_false))
&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;False
&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; f_repr(f_or(f_true,  f_false))
&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;True
&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; f_repr(f_or(f_false, f_true))
&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;True
&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; f_repr(f_or(f_true, f_true))
&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;True
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we have OR and NOT, we can make all other gates, e.g.:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-python"&gt;&lt;span&gt;f_and = &lt;/span&gt;&lt;span style="color: #215da8;"&gt;lambda &lt;/span&gt;&lt;span&gt;a, b: f_not(f_or(f_not(a), f_not(b)))
&lt;/span&gt;&lt;span&gt;f_xor = &lt;/span&gt;&lt;span style="color: #215da8;"&gt;lambda &lt;/span&gt;&lt;span&gt;a, b: f_or(f_and(f_not(a), b), f_and(a, f_not(b)))
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class="language-python " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-python"&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; f_repr(f_and(f_false, f_false))
&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;False
&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; f_repr(f_and(f_true,  f_false))
&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;False
&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; f_repr(f_and(f_false, f_true))
&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;False
&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; f_repr(f_and(f_true, f_true))
&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;True
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; f_repr(f_xor(f_false, f_false))
&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;False
&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; f_repr(f_xor(f_true,  f_false))
&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;True
&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; f_repr(f_xor(f_false, f_true))
&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;True
&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; f_repr(f_xor(f_true, f_true))
&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;False
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="software-integers"&gt;&lt;a class="anchor" href="#software-integers"&gt;Software integers&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You may have heard of soft-float, software implementations of floating point
using integers. Let’s turn that on its head: an integer implementation done in
software, using only floating point ops. Let’s do it in Rust so we can look at
the final assembly output to see how &lt;del&gt;horrifically slow&lt;/del&gt; awesome it is.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #215da8;"&gt;type &lt;/span&gt;&lt;span&gt;Bit = &lt;/span&gt;&lt;span style="color: #215da8;"&gt;f32&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span style="color: #215da8;"&gt;const &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;ZERO&lt;/span&gt;&lt;span&gt;: Bit = -&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0.0&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span style="color: #215da8;"&gt;const &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;ONE&lt;/span&gt;&lt;span&gt;: Bit = &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0.0&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #215da8;"&gt;fn &lt;/span&gt;&lt;span&gt;not(x: Bit) -&amp;gt; Bit { &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;ZERO &lt;/span&gt;&lt;span&gt;- x }
&lt;/span&gt;&lt;span style="color: #215da8;"&gt;fn &lt;/span&gt;&lt;span&gt;or(a: Bit, b: Bit) -&amp;gt; Bit { a - not(b) }
&lt;/span&gt;&lt;span style="color: #215da8;"&gt;fn &lt;/span&gt;&lt;span&gt;and(a: Bit, b: Bit) -&amp;gt; Bit { not(or(not(a), not(b))) }
&lt;/span&gt;&lt;span style="color: #215da8;"&gt;fn &lt;/span&gt;&lt;span&gt;xor(a: Bit, b: Bit) -&amp;gt; Bit { or(and(not(a), b), and(a, not(b))) }
&lt;/span&gt;&lt;span style="color: #215da8;"&gt;fn &lt;/span&gt;&lt;span&gt;adder(a: Bit, b: Bit, c: Bit) -&amp;gt; (Bit, Bit) {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #215da8;"&gt;let&lt;/span&gt;&lt;span&gt; s = xor(xor(a, b), c);
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #215da8;"&gt;let&lt;/span&gt;&lt;span&gt; c = or(and(xor(a, b), c), and(a, b));
&lt;/span&gt;&lt;span&gt;    (s, c)
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #215da8;"&gt;type &lt;/span&gt;&lt;span&gt;SoftU8 = [Bit; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;8&lt;/span&gt;&lt;span&gt;];
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;pub &lt;/span&gt;&lt;span style="color: #215da8;"&gt;fn &lt;/span&gt;&lt;span&gt;softu8_add(a: SoftU8, b: SoftU8) -&amp;gt; SoftU8 {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #215da8;"&gt;let &lt;/span&gt;&lt;span&gt;(s0, c) = adder(a[&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0&lt;/span&gt;&lt;span&gt;], b[&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;ZERO&lt;/span&gt;&lt;span&gt;);
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #215da8;"&gt;let &lt;/span&gt;&lt;span&gt;(s1, c) = adder(a[&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1&lt;/span&gt;&lt;span&gt;], b[&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1&lt;/span&gt;&lt;span&gt;], c);
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #215da8;"&gt;let &lt;/span&gt;&lt;span&gt;(s2, c) = adder(a[&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;2&lt;/span&gt;&lt;span&gt;], b[&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;2&lt;/span&gt;&lt;span&gt;], c);
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #215da8;"&gt;let &lt;/span&gt;&lt;span&gt;(s3, c) = adder(a[&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;3&lt;/span&gt;&lt;span&gt;], b[&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;3&lt;/span&gt;&lt;span&gt;], c);
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #215da8;"&gt;let &lt;/span&gt;&lt;span&gt;(s4, c) = adder(a[&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;4&lt;/span&gt;&lt;span&gt;], b[&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;4&lt;/span&gt;&lt;span&gt;], c);
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #215da8;"&gt;let &lt;/span&gt;&lt;span&gt;(s5, c) = adder(a[&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;5&lt;/span&gt;&lt;span&gt;], b[&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;5&lt;/span&gt;&lt;span&gt;], c);
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #215da8;"&gt;let &lt;/span&gt;&lt;span&gt;(s6, c) = adder(a[&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;6&lt;/span&gt;&lt;span&gt;], b[&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;6&lt;/span&gt;&lt;span&gt;], c);
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #215da8;"&gt;let &lt;/span&gt;&lt;span&gt;(s7, _) = adder(a[&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;7&lt;/span&gt;&lt;span&gt;], b[&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;7&lt;/span&gt;&lt;span&gt;], c);
&lt;/span&gt;&lt;span&gt;    [s0, s1, s2, s3, s4, s5, s6, s7]
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #76647b;"&gt;// Hmm? u8? What's that? Shhhh....
&lt;/span&gt;&lt;span&gt;pub &lt;/span&gt;&lt;span style="color: #215da8;"&gt;fn &lt;/span&gt;&lt;span&gt;to_softu8(x: &lt;/span&gt;&lt;span style="color: #215da8;"&gt;u8&lt;/span&gt;&lt;span&gt;) -&amp;gt; SoftU8 {
&lt;/span&gt;&lt;span&gt;    std::array::from_fn(|i| &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;if &lt;/span&gt;&lt;span&gt;(x &amp;gt;&amp;gt; i) &amp;amp; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1 &lt;/span&gt;&lt;span&gt;== &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1 &lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;ONE &lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;else &lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;ZERO &lt;/span&gt;&lt;span&gt;})
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;pub &lt;/span&gt;&lt;span style="color: #215da8;"&gt;fn &lt;/span&gt;&lt;span&gt;from_softu8(x: SoftU8) -&amp;gt; &lt;/span&gt;&lt;span style="color: #215da8;"&gt;u8 &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;    (&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0&lt;/span&gt;&lt;span&gt;..&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;8&lt;/span&gt;&lt;span&gt;).filter(|i| x[*i].signum() &amp;gt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0.0&lt;/span&gt;&lt;span&gt;).map(|i| &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1 &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt; i).sum()
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #215da8;"&gt;fn &lt;/span&gt;&lt;span&gt;main() {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #215da8;"&gt;let&lt;/span&gt;&lt;span&gt; a = to_softu8(&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;23&lt;/span&gt;&lt;span&gt;);
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #215da8;"&gt;let&lt;/span&gt;&lt;span&gt; b = to_softu8(&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;19&lt;/span&gt;&lt;span&gt;);
&lt;/span&gt;&lt;span&gt;    println!(&lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;{}&lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;, from_softu8(softu8_add(a, b)));
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s horrible, but it works, it dutifully prints 42. And it &lt;em&gt;only&lt;/em&gt; took $\approx 120$
floating point instructions to add two 8-bit integers:&lt;/p&gt;
&lt;aside&gt;
On x86-64 there isn't actually a floating point negation instruction, instead
the compiler simply emits a XOR with a mask that toggles the top bit, which
is the sign bit of a IEEE-754 floating point number.
&lt;/aside&gt;
&lt;pre class="language-asm " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-asm"&gt;&lt;span&gt;example::softu8_add:
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;mov     &lt;/span&gt;&lt;span&gt;rax, rdi
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movups  &lt;/span&gt;&lt;span&gt;xmm2, xmmword ptr [rsi]
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movups  &lt;/span&gt;&lt;span&gt;xmm0, xmmword ptr [rdx]
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movaps  &lt;/span&gt;&lt;span&gt;xmm3, xmm2
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subps   &lt;/span&gt;&lt;span&gt;xmm3, xmm0
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movaps  &lt;/span&gt;&lt;span&gt;xmm4, xmm0
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subps   &lt;/span&gt;&lt;span&gt;xmm4, xmm2
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movaps  &lt;/span&gt;&lt;span&gt;xmm1, xmmword ptr [rip + .LCPI0_0]
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;xorps   &lt;/span&gt;&lt;span&gt;xmm4, xmm1
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subps   &lt;/span&gt;&lt;span&gt;xmm4, xmm3
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;xorps   &lt;/span&gt;&lt;span&gt;xmm3, xmm3
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subss   &lt;/span&gt;&lt;span&gt;xmm3, xmm4
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movaps  &lt;/span&gt;&lt;span&gt;xmm6, xmm0
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;xorps   &lt;/span&gt;&lt;span&gt;xmm6, xmm1
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subss   &lt;/span&gt;&lt;span&gt;xmm6, xmm2
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;xorps   &lt;/span&gt;&lt;span&gt;xmm6, xmm1
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subss   &lt;/span&gt;&lt;span&gt;xmm6, xmm3
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movaps  &lt;/span&gt;&lt;span&gt;xmm3, xmm4
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;shufps  &lt;/span&gt;&lt;span&gt;xmm3, xmm4, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;85
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movaps  &lt;/span&gt;&lt;span&gt;xmm5, xmm6
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subss   &lt;/span&gt;&lt;span&gt;xmm5, xmm3
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;xorps   &lt;/span&gt;&lt;span&gt;xmm5, xmm1
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movaps  &lt;/span&gt;&lt;span&gt;xmm10, xmm6
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;xorps   &lt;/span&gt;&lt;span&gt;xmm10, xmm1
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subss   &lt;/span&gt;&lt;span&gt;xmm10, xmm3
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movaps  &lt;/span&gt;&lt;span&gt;xmm7, xmm0
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;shufps  &lt;/span&gt;&lt;span&gt;xmm7, xmm0, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;85
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;xorps   &lt;/span&gt;&lt;span&gt;xmm7, xmm1
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movaps  &lt;/span&gt;&lt;span&gt;xmm3, xmm2
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;shufps  &lt;/span&gt;&lt;span&gt;xmm3, xmm2, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;85
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subss   &lt;/span&gt;&lt;span&gt;xmm7, xmm3
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;xorps   &lt;/span&gt;&lt;span&gt;xmm7, xmm1
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movaps  &lt;/span&gt;&lt;span&gt;xmm8, xmm4
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;unpckhpd        &lt;/span&gt;&lt;span&gt;xmm8, xmm4
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movaps  &lt;/span&gt;&lt;span&gt;xmm3, xmm0
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;unpckhpd        &lt;/span&gt;&lt;span&gt;xmm3, xmm0
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;xorps   &lt;/span&gt;&lt;span&gt;xmm3, xmm1
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movaps  &lt;/span&gt;&lt;span&gt;xmm9, xmm2
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;unpckhpd        &lt;/span&gt;&lt;span&gt;xmm9, xmm2
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subss   &lt;/span&gt;&lt;span&gt;xmm3, xmm9
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;xorps   &lt;/span&gt;&lt;span&gt;xmm3, xmm1
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;xorps   &lt;/span&gt;&lt;span&gt;xmm11, xmm11
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movaps  &lt;/span&gt;&lt;span&gt;xmm9, xmm4
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;shufps  &lt;/span&gt;&lt;span&gt;xmm9, xmm4, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;255
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subss   &lt;/span&gt;&lt;span&gt;xmm7, xmm10
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movaps  &lt;/span&gt;&lt;span&gt;xmm10, xmm7
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;xorps   &lt;/span&gt;&lt;span&gt;xmm10, xmm1
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subss   &lt;/span&gt;&lt;span&gt;xmm10, xmm8
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subss   &lt;/span&gt;&lt;span&gt;xmm3, xmm10
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;unpcklps        &lt;/span&gt;&lt;span&gt;xmm7, xmm3
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;shufps  &lt;/span&gt;&lt;span&gt;xmm6, xmm7, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;64
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;addps   &lt;/span&gt;&lt;span&gt;xmm11, xmm4
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movlhps &lt;/span&gt;&lt;span&gt;xmm5, xmm4
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subps   &lt;/span&gt;&lt;span&gt;xmm4, xmm6
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movss   &lt;/span&gt;&lt;span&gt;xmm4, xmm11
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subps   &lt;/span&gt;&lt;span&gt;xmm7, xmm8
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;xorps   &lt;/span&gt;&lt;span&gt;xmm7, xmm1
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;shufps  &lt;/span&gt;&lt;span&gt;xmm5, xmm7, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;66
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subps   &lt;/span&gt;&lt;span&gt;xmm5, xmm4
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;xorps   &lt;/span&gt;&lt;span&gt;xmm3, xmm1
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subss   &lt;/span&gt;&lt;span&gt;xmm3, xmm9
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;shufps  &lt;/span&gt;&lt;span&gt;xmm0, xmm0, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;255
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;xorps   &lt;/span&gt;&lt;span&gt;xmm0, xmm1
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;shufps  &lt;/span&gt;&lt;span&gt;xmm2, xmm2, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;255
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subss   &lt;/span&gt;&lt;span&gt;xmm0, xmm2
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;xorps   &lt;/span&gt;&lt;span&gt;xmm0, xmm1
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movups  &lt;/span&gt;&lt;span&gt;xmmword ptr [rdi], xmm5
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movups  &lt;/span&gt;&lt;span&gt;xmm2, xmmword ptr [rdx + &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;16&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movaps  &lt;/span&gt;&lt;span&gt;xmm5, xmm2
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;xorps   &lt;/span&gt;&lt;span&gt;xmm5, xmm1
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movups  &lt;/span&gt;&lt;span&gt;xmm7, xmmword ptr [rsi + &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;16&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subss   &lt;/span&gt;&lt;span&gt;xmm5, xmm7
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;xorps   &lt;/span&gt;&lt;span&gt;xmm5, xmm1
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movaps  &lt;/span&gt;&lt;span&gt;xmm4, xmm2
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;shufps  &lt;/span&gt;&lt;span&gt;xmm4, xmm2, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;85
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;xorps   &lt;/span&gt;&lt;span&gt;xmm4, xmm1
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movaps  &lt;/span&gt;&lt;span&gt;xmm6, xmm2
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movaps  &lt;/span&gt;&lt;span&gt;xmm8, xmm7
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movaps  &lt;/span&gt;&lt;span&gt;xmm9, xmm7
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subps   &lt;/span&gt;&lt;span&gt;xmm9, xmm2
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subps   &lt;/span&gt;&lt;span&gt;xmm2, xmm7
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;shufps  &lt;/span&gt;&lt;span&gt;xmm7, xmm7, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;85
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subss   &lt;/span&gt;&lt;span&gt;xmm4, xmm7
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;xorps   &lt;/span&gt;&lt;span&gt;xmm4, xmm1
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movhlps &lt;/span&gt;&lt;span&gt;xmm6, xmm6
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;xorps   &lt;/span&gt;&lt;span&gt;xmm6, xmm1
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movhlps &lt;/span&gt;&lt;span&gt;xmm8, xmm8
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subss   &lt;/span&gt;&lt;span&gt;xmm6, xmm8
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;xorps   &lt;/span&gt;&lt;span&gt;xmm6, xmm1
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;xorps   &lt;/span&gt;&lt;span&gt;xmm2, xmm1
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subps   &lt;/span&gt;&lt;span&gt;xmm2, xmm9
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subss   &lt;/span&gt;&lt;span&gt;xmm0, xmm3
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movaps  &lt;/span&gt;&lt;span&gt;xmm3, xmm0
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;xorps   &lt;/span&gt;&lt;span&gt;xmm3, xmm1
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subss   &lt;/span&gt;&lt;span&gt;xmm3, xmm2
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subss   &lt;/span&gt;&lt;span&gt;xmm5, xmm3
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;unpcklps        &lt;/span&gt;&lt;span&gt;xmm0, xmm5
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;xorps   &lt;/span&gt;&lt;span&gt;xmm5, xmm1
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movaps  &lt;/span&gt;&lt;span&gt;xmm3, xmm2
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;shufps  &lt;/span&gt;&lt;span&gt;xmm3, xmm2, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;85
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subss   &lt;/span&gt;&lt;span&gt;xmm5, xmm3
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subss   &lt;/span&gt;&lt;span&gt;xmm4, xmm5
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movaps  &lt;/span&gt;&lt;span&gt;xmm3, xmm4
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;xorps   &lt;/span&gt;&lt;span&gt;xmm3, xmm1
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movaps  &lt;/span&gt;&lt;span&gt;xmm5, xmm2
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;unpckhpd        &lt;/span&gt;&lt;span&gt;xmm5, xmm2
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subss   &lt;/span&gt;&lt;span&gt;xmm3, xmm5
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subss   &lt;/span&gt;&lt;span&gt;xmm6, xmm3
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;unpcklps        &lt;/span&gt;&lt;span&gt;xmm4, xmm6
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movlhps &lt;/span&gt;&lt;span&gt;xmm0, xmm4
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movaps  &lt;/span&gt;&lt;span&gt;xmm3, xmm2
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subps   &lt;/span&gt;&lt;span&gt;xmm3, xmm0
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subps   &lt;/span&gt;&lt;span&gt;xmm0, xmm2
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;xorps   &lt;/span&gt;&lt;span&gt;xmm0, xmm1
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;subps   &lt;/span&gt;&lt;span&gt;xmm0, xmm3
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;movups  &lt;/span&gt;&lt;span&gt;xmmword ptr [rdi + &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;16&lt;/span&gt;&lt;span&gt;], xmm0
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;ret
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</description><author>orlp.net - Blog Archive</author><pubDate>Thu, 28 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://orlp.net/blog/subtraction-is-functionally-complete/</guid></item><item><title>Docker Compose Dial Tcp Lookup No Such Host</title><link>https://studiofreya.org/posts/docker-compose-dial-tcp-lookup-no-such-host/</link><description>&lt;p&gt;If you happen to be working with Docker and Docker Compose, with multiple services, and you get
&lt;code&gt;E! Write error: Post \&amp;quot;http://host:1234/api/v2/write?bucket=http-logs\&amp;quot;: dial tcp: lookup influxdb on 127.0.0.11:53: no such host, batch kept for retrying&amp;quot;}&lt;/code&gt;, make sure both the client and server are on the same docker network.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-yaml"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #f92672;"&gt;version&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"3.7"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #f92672;"&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #f92672;"&gt;caddy&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;networks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;      - &lt;span style="color: #ae81ff;"&gt;monitoring&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #f92672;"&gt;influxdb&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;networks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;      - &lt;span style="color: #ae81ff;"&gt;monitoring&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #f92672;"&gt;networks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #f92672;"&gt;monitoring&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><author>Studiofreya SSG Site</author><pubDate>Thu, 28 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://studiofreya.org/posts/docker-compose-dial-tcp-lookup-no-such-host/</guid></item><item><title>Publishing my first game using pico-8</title><link>https://boyter.org/posts/publishing-a-pico8-game/</link><description>&lt;p&gt;TL/DR; &lt;a href="https://boyter.itch.io/wizard-duel"&gt;Play/Download a copy from itch.io&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I had always wanted to create games. In fact its one of the reasons I went down the development path. For various reasons I never invested enough time (probably too much time playing them) and went into writing code for businesses.&lt;/p&gt;
&lt;p&gt;In hindsight that was probably the right move from a financial perspective, since it seems most game developers tend to be fairly underpaid and the drama in the game development space makes high school teen drama look tame.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Thu, 28 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/publishing-a-pico8-game/</guid></item><item><title>Hijacking Bluesky Identities with a Malleable Deputy</title><link>https://www.da.vidbuchanan.co.uk/blog/hacking-bluesky.html</link><author>David Buchanan's Blog</author><pubDate>Thu, 28 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.da.vidbuchanan.co.uk/blog/hacking-bluesky.html</guid></item><item><title>An Extra Special Family Vacation</title><link>https://jasoneckert.github.io/myblog/special-vacation/</link><description>&lt;p&gt;&lt;img alt="Family" src="family.jpg#center" title="Family" /&gt;&lt;/p&gt;
&lt;p&gt;While most of my blog posts are technical in nature, I occasionally post a personal one - and this one certainly falls into that category.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been on vacation these past several weeks to spend some quality time with my daughter, her partner, and my grandson from Australia. It was the first time I&amp;rsquo;ve been able to hug my daughter in four years, and the first time I&amp;rsquo;ve been able to see my grandson and his father outside of Zoom.&lt;/p&gt;</description><author>Jason Eckert's Website and Blog</author><pubDate>Thu, 28 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jasoneckert.github.io/myblog/special-vacation/</guid></item><item><title>Excellent Quotes from Marcus Aurelius’ Meditations</title><link>https://jdsalaro.com/bookmark/quote/marcus-aurelius-meditations/</link><description>While on a military campaign which started in 170 and ended in 180, Marcus Aurelius1 wrote his Meditations in Greek for his own guidance and self-development. The original title of the work, if it had one, is unknown. ‘Meditations’ – as well as other titles including ‘To Himself’ – were adopted later. He had a logical mind, and his notes were representative of Stoic philosophy and spirituality.</description><author>Jayson Salazar Rodriguez | @jdsalaro | Blog</author><pubDate>Thu, 28 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jdsalaro.com/bookmark/quote/marcus-aurelius-meditations/</guid></item><item><title>Benjamin Franklin on the Tension between Liberty, Virtue, Safety, Wealth and Power</title><link>https://jdsalaro.com/bookmark/quote/benjamin-franklin-liberty-security-fear-wealth-power/</link><description>Those who would give up essential Liberty, to purchase a little temporary Safety, deserve neither Liberty nor Safety.</description><author>Jayson Salazar Rodriguez | @jdsalaro | Blog</author><pubDate>Thu, 28 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jdsalaro.com/bookmark/quote/benjamin-franklin-liberty-security-fear-wealth-power/</guid></item><item><title>Bug Hunting</title><link>http://evantravers.com/articles/2023/09/27/bug-hunting/</link><description>&lt;p&gt;Talking with some new colleagues at work, this old programming war story from a decade ago came to mind. My memory on all the details is fuzzy&lt;sup class="footnote-ref"&gt;&lt;a href="#fn-1" id="fnref-1"&gt;1&lt;/a&gt;&lt;/sup&gt; but here's what I recall:&lt;/p&gt;
&lt;p&gt;I was working for a web development company doing a lot of local e-commerce. Everything was on FTP, one staging server and one live server. While I was using git for personal projects (even though the computer science department in college didn't teach it!) I tried to evangelize it at work, but people thought it was needlessly complex and time-wasting. 🤷&lt;/p&gt;
&lt;p&gt;I did a lot of support tickets and content changes for clients, but had somewhat specialized in a complicated wedding registry script that we often sold to e-commerce clients.&lt;/p&gt;
&lt;p&gt;That day I got a familiar tasking: an older customer wanted the additional feature I had programmed for a newer client.&lt;/p&gt;
&lt;p&gt;I copied the newer registry file over to the client folder, updated the client-specific IDs in the SQL queries, added the feature, refreshed my browser and clicked around to dev test my work. I logged my time, and sent the URL over to the project manager to say I was done, and moved on to the next support ticket.&lt;/p&gt;
&lt;p&gt;My project manager Google Chat'd me: &amp;quot;hey man, this isn't done.&amp;quot; Annoyed, I dropped the task I was now working and clicked back over and sure enough the page was broken. I opened up the file, scanned through and made all the changes again. This time I browser tested in both my browser and the project manager's preferred variant, and marked it as done. After lunch I got another message: &amp;quot;Still broken.&amp;quot;&lt;/p&gt;
&lt;p&gt;Over the next few days this loop continued. I'd fix the bug, it would work for me, I'd send it off to the PM, and he would lightly scold me for not finishing my work. I'd wrap up whatever I was then fixing, take another stab at the changes, and the cycle would continue.&lt;/p&gt;
&lt;p&gt;Needless to say, my project manager and I were both getting really annoyed with each other.&lt;/p&gt;
&lt;p&gt;I was encountering some kind of bug… but I was having a hard time determining what was breaking. As a junior developer there were a lot of systems at play that I didn't understand: a shared database with live and changing data, CSS and HTML bugs between browsers, and a complex homebrew CMS with no documentation. I didn't yet have the system understanding to determine what system drove what behavior… so I tried brute force for far too long.&lt;/p&gt;
&lt;p&gt;Finally I sat down, stopped multitasking, and slowly worked through the problem, instrumenting the solution with print-to-screen logging and comments… until I yelled with victory and anger.&lt;/p&gt;
&lt;p&gt;I walked into the next room, unplugged a computer, waited six hours, and the application was fixed permanently.&lt;/p&gt;
&lt;h2&gt;&lt;a class="anchor" href="#" id=""&gt;&lt;/a&gt;…&lt;/h2&gt;
&lt;p&gt;While working on the logging I saw that the file had magically reverted back to the start of the process. Using my FTP client I inspected the file and discovered that the last modified username was from an employee that had been on vacation that week…&lt;/p&gt;
&lt;p&gt;I was right, and my project manager was right. I had correctly installed the registry, but my changes had not stayed on the server.&lt;/p&gt;
&lt;p&gt;It turns out that the other developer had opened the whole folder in Homesite+ on his work laptop and then left. He had set his editor to autosave his open work every five minutes or so… and therefore his sleeping computer was clobbering my work, usually right around the time I would send it to my Project Manager.&lt;/p&gt;
&lt;p&gt;With permission from the boss, I unplugged his laptop, waited for the battery to die, then finished the feature in seconds… I had already written it a hundred times already.&lt;/p&gt;
&lt;p&gt;After this horribleness… I learned how to use &lt;a href="https://github.com/git-ftp/git-ftp"&gt;git-ftp&lt;/a&gt; and never lost my work again… even though the company never used git while I was there.&lt;/p&gt;
&lt;section class="footnotes"&gt;
&lt;ol&gt;
&lt;li id="fn-1"&gt;
&lt;p&gt;I wasn't then the journaling freak that I am now… &lt;a class="footnote-backref" href="#fnref-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</description><author>trv.rs</author><pubDate>Wed, 27 Sep 2023 14:10:00 GMT</pubDate><guid isPermaLink="true">http://evantravers.com/articles/2023/09/27/bug-hunting/</guid></item><item><title>What is different?</title><link>https://ilearnt.com/blog/culturefeedback/</link><description>&lt;p&gt;Every Monday morning as part of our stand up meeting we ask a &amp;ldquo;question of the day&amp;rdquo;. A designated &lt;del&gt;victim&lt;/del&gt; person chooses a question that each team member gets to answer. This week the question that was asked was:&lt;/p&gt;</description><author>I Learnt</author><pubDate>Wed, 27 Sep 2023 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/culturefeedback/</guid></item><item><title>Request for Comment: Works in Progress Retreat</title><link>https://cmart.blog/coding-retreat-rfc/</link><description>&lt;h2 id="an-invitation-for-software-developers-makers-and-similar-creators"&gt;An invitation for software developers, makers, and similar creators&lt;/h2&gt;
&lt;p&gt;Is there a project that you&amp;rsquo;d love to work on, but work and life get in the way? Join a week-long retreat to incubate or evolve your project in the energizing company of others doing the same. My goal is to enable our self-actualization. We&amp;rsquo;ll hack on our projects together in an inviting and comfortable space, share meals and laughs, and at the end, take turns presenting our creations to the group. Along the way, we may collect new friends, new ideas, and/or new career directions. I&amp;rsquo;m not trying to make money: you pay a nominal fee to cover the cost of the venue, and arrange your own travel and accommodation nearby.&lt;/p&gt;</description><author>cmart's blog</author><pubDate>Wed, 27 Sep 2023 11:29:00 GMT</pubDate><guid isPermaLink="true">https://cmart.blog/coding-retreat-rfc/</guid></item><item><title>Displaying Local Times in Next.js</title><link>https://francoisbest.com/posts/2023/displaying-local-times-in-nextjs?utm_source=rss</link><description>Making time accessible by dealing with React SSR hydration mismatches across timezones.</description><author>Articles by François Best</author><pubDate>Wed, 27 Sep 2023 11:00:00 GMT</pubDate><guid isPermaLink="true">https://francoisbest.com/posts/2023/displaying-local-times-in-nextjs?utm_source=rss</guid></item><item><title>Bizarre Error while compiling a Rust program</title><link>https://porkrind.org/missives/bizarre-error-while-compiling-a-rust-program/</link><description>I was trying to compile a third party Rust crate today on a Debian box and I was getting a weird error I&amp;#8217;d never seen: = note: cc: fatal error: cannot read spec file './specs': Is a directory compilation terminated. I&amp;#8217;ve done a bunch of embedded C stuff in the past so I knew what &amp;#8230; &lt;a class="more-link" href="https://porkrind.org/missives/bizarre-error-while-compiling-a-rust-program/"&gt;Continue reading &lt;span class="screen-reader-text"&gt;Bizarre Error while compiling a Rust program&lt;/span&gt;&lt;/a&gt;</description><author>Missives</author><pubDate>Wed, 27 Sep 2023 07:16:33 GMT</pubDate><guid isPermaLink="true">https://porkrind.org/missives/bizarre-error-while-compiling-a-rust-program/</guid></item><item><title>Disable Firefox Translation</title><link>https://xnacly.me/posts/2023/disable-firefox-translation/</link><description>Disabling popup prompt and translation in firefox@^118</description><author>xnacly - blog</author><pubDate>Wed, 27 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xnacly.me/posts/2023/disable-firefox-translation/</guid></item><item><title>"Somebody Tranq That Child!"</title><link>https://taylor.town/child-tranqs</link><description>Modern blowguns are Swiss-army-flavored. A baseline model retails for $299 and acts as a telescope, microscope, megaphone, pencil, camera, snorkel, food thermometer, pregnancy test, and flute.</description><author>taylor.town</author><pubDate>Wed, 27 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/child-tranqs</guid></item><item><title>How to Determine the Entrypoint for a Docker Image</title><link>https://nelson.cloud/how-to-determine-the-entrypoint-for-a-docker-image/?ref=rss</link><description>Using &lt;code&gt;docker inspect&lt;/code&gt; to determine the entrypoint for a given docker image.</description><author>Nelson Figueroa</author><pubDate>Wed, 27 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://nelson.cloud/how-to-determine-the-entrypoint-for-a-docker-image/?ref=rss</guid></item><item><title>Changing of the Seasons</title><link>https://olivergilan.com/changing-of-the-seasons/</link><description>Winter brings with it hard times and dark days but I am remembering what it means to find beauty in every season and what type of life allows me to do that.</description><author>Oliver Gilan</author><pubDate>Wed, 27 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://olivergilan.com/changing-of-the-seasons/</guid></item><item><title>Organizing Notes in 3D with AI</title><link>https://briansunter.com/newsletter/issue-14</link><description>Using machine learning to automatically organize notes by meaning and exploring them in a browser 3D interface</description><author>Brian Sunter</author><pubDate>Wed, 27 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://briansunter.com/newsletter/issue-14</guid></item><item><title>Mt Shasta</title><link>https://ellie.wtf/life/mt-shasta/</link><description>&lt;p&gt;&lt;img alt="" src="https://img.ellie.wtf/i/77e3db766d18e651f2c8e469375cb857312779e3c5d34b09b9707ddc38a2f0d4.jpeg" /&gt;&lt;/p&gt;
&lt;p&gt;I'm writing this one after a longggg day of riding, including heading home in the dark with a tinted visor 😬 Probably going to keep it brief. Today we rode 280 miles!&lt;/p&gt;
&lt;p&gt;Today I woke up around 6:30, made some tea, and went to sit outside while the world wakes up. There are so many woodpecker sounds here! We had planned to ride to Mt Shasta, which has been one of the things I really wanted to see on this trip.&lt;/p&gt;
&lt;p&gt;Once Michelle was awake, we headed into Weaverville for breakfast. We went to this place called "Mamma Llama", and I learned that an "English Muffin" is indeed what we call a "Muffin" in England, and not the English version of something American.&lt;/p&gt;
&lt;p&gt;On the way up highway 3, we stopped by a lake. I probably could have sat by the water all day long&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://img.ellie.wtf/i/6d52a2abe726e51c6020f8d2649afbb007fb3317f4c22b1aa4f426220bcb6895.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;The whole time we'd been riding through the forest, I'd had to dodge squirrels every few miles. They dash out super quickly across the road, which explains why there were so many dead ones all over the place. Despite my best efforts, I hit one just after leaving the lake :( Sorry little squirrel!&lt;/p&gt;
&lt;p&gt;This part of the road was also especially dirty, and the banks showed evidence of landslides. In places the surface was almost entirely covered in clay&lt;/p&gt;
&lt;p&gt;We spent a good while riding through the mountains, and it felt like the road would go on forever. It was quiet, twisty, and had an excellent surface. The only other people we saw were semi-regular road works, and trucks for servicing those works.&lt;/p&gt;
&lt;p&gt;One thing I saw was that they have "pilot trucks" to escort you past all the works. In some cases we rode on totally unpaved roads, that were very rough and dirty. Back home, they'd probably just close the road - but here, I guess there are few alternate routes.&lt;/p&gt;
&lt;div class="img-pair"&gt;
&lt;img src="https://img.ellie.wtf/i/560793da8b7eeca7f4254fcb38a26a3c41021fef86d707af3a7f3724058cc5f7.JPG" /&gt;
&lt;img src="https://img.ellie.wtf/i/e8a0bda5f83c19b7c167f45749f46a853713d01fa4af554d343f50b206c399e6.JPG" /&gt;
&lt;/div&gt;
&lt;p&gt;We rounded another corner, and there it was! A snowy peak poking above the trees.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://img.ellie.wtf/i/fc823c94b9c741f147857522284a1221de915da5174deb34ad413798dcfb254e.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;The next stop was a town called Weed. It was well positioned for a fuel stop before heading to Mt Shasta. There were a lot of gift stores selling T shirts with "I ❤️ Weed" written on them&lt;/p&gt;
&lt;p&gt;The ride up the mountain was fantastic, with the main issue being paying attention to the road - as we climbed, the views just kept getting better. Eventually the temperature dropped to 12C/55F, making me very glad I was wearing thermals.&lt;/p&gt;
&lt;p&gt;We rode up as high as was possible, and found lots of people camping with some people seeming to be praying. Apparently the mountain holds some sort of spiritual meaning to some people, and has a particularly sacred meaning to Native Americans. I'm not sure on the details there though, beyond what I can Google.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://img.ellie.wtf/i/0d104f69bd993357b026734eae1be9fda6b99b4900bf841539199d2dcd372755.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://img.ellie.wtf/i/7912a1826ab2de6f9b7779c0e3a171e85d177a6de4286bbec16b0514246b603d.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;On the way home, we stopped once more in Weed for fuel and snacks. We also explored the local florist!&lt;/p&gt;
&lt;p&gt;After that, we had about 2 hours riding until dinner. Michelle had planned the route to take us Forest Road 17. It reminded me of the country lanes in the UK, except with much more pine trees and mountains. There had also been several rockslides, with some boulders on the road around corners. We had a few final glimpses of Mt Shasta, before it grew too distant&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://img.ellie.wtf/i/54647e2d6537b136019c3c895b8bf8e0407d80909f04beaca912bb599e85b84a.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Highway 3 was the main route home, and we had a really lucky run - absolutely no cars, just one flowing corner after another.&lt;/p&gt;
&lt;p&gt;We wrapped up the day with dinner at Cafe on Main in Weaverville, which served &lt;em&gt;excellent&lt;/em&gt; food.&lt;/p&gt;</description><author>Ellie's Notes</author><pubDate>Wed, 27 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ellie.wtf/life/mt-shasta/</guid></item><item><title>Reiterate 1.6.0 release</title><link>https://blog.reiterate.app/software/2023/09/26/reiterate-1-6-0-release/</link><description>Version 1.6.0 of Reiterate has been released and is now available.</description><author>Reiterations</author><pubDate>Wed, 27 Sep 2023 00:23:00 GMT</pubDate><guid isPermaLink="true">https://blog.reiterate.app/software/2023/09/26/reiterate-1-6-0-release/</guid></item><item><title>"Retrocorder" - Using a spare PC to make a fully automated retro-gaming recording solution</title><link>https://datalars.com/2023/09/26/retrocorder-make-an-automated-retro-gaming-recording-solution/</link><description>&lt;h2 id="post-16-persistent-nostalgia"&gt;Persistent nostalgia&lt;/h2&gt;
&lt;p&gt;The past few months, I've spent some time to set up a room in my house to be a pretty sweet space dedicated to retro-gaming. This includes a vintage PC, a CRT TV, and a multitude of classic games consoles hooked up to four RCA/AV switches. This works well, and to play the console we want, one can just flip a couple of switches instead of having to unplug and shuffle around literally 8 different sets of RCA cables.&lt;/p&gt;
&lt;p&gt;Thing is, when you're playing games, especially during couch coop, &lt;em&gt;things tend to happen&lt;/em&gt;. Some of these things, depending on the types of games you play, can be utterly hilarious, and worth saving for posterity. With the two last generations of consoles I own, PS5, PS4 and the Nintendo Switch, you can just press a single button on the controller to save recent gameplay as a file to the system storage, which is awesome. When you also enjoy playing on 20-30 year old game consoles as well, this part becomes a bit trickier.&lt;/p&gt;
&lt;h2 id="post-16-emergent-technologies"&gt;Emergent technologies&lt;/h2&gt;
&lt;p&gt;Enter: The &lt;strong&gt;&amp;quot;Retrocorder&amp;quot;&lt;/strong&gt; - the dedicated computer I've set up to continuously record all retro gaming gameplay as it happens.&lt;/p&gt;
&lt;p&gt;For reference, this is what my setup looked like &lt;em&gt;before&lt;/em&gt; Retrocorder:&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://datalars.com/includes/thumbnail.php?file=retrogaming-chart-before-2.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;small&gt;&lt;em&gt;(If I stick with 3-port switches, I can only get one more console before I have to add another level to the hierarchy&lt;/em&gt; 😳)&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;Since all the signals involved here are analog, you'd think there would be some degradation at each step. That might be true, but none are perceptible to me in regards to image or sound quality, and the cables between the switches are short, minimizing signal loss.&lt;/p&gt;
&lt;h2 id="post-16-hardware"&gt;Hardware&lt;/h2&gt;
&lt;p&gt;If you want to follow along at home, here's a parts list:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Spare PC with a processor powerful enough to record and encode 480p/576p video in real time (using h264 this roughly means any CPU from the last 10 or so years)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;One of the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://s.click.aliexpress.com/e/_DnNJDBJ"&gt;HDMI capture card&lt;/a&gt; (recommended)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://s.click.aliexpress.com/e/_DeUGWYH"&gt;RCA/AV capture card&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://s.click.aliexpress.com/e/_DE6yl6z"&gt;AV2HDMI converter&lt;/a&gt; (if using HDMI capture card)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3x &lt;a href="https://s.click.aliexpress.com/e/_DmnHlFr"&gt;RCA Y-splitters&lt;/a&gt; (RCA female to 2RCA male)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3-6x &lt;a href="https://s.click.aliexpress.com/e/_Dn4vfTn"&gt;RCA cables&lt;/a&gt; (female to male)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;small&gt;&lt;em&gt;The above links are affiliate links. If you buy something using them, I may earn a small commission at no extra cost to you.&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;Using these parts, we can split the RCA cables right before the TV, and lead one end to the TV, and the other into our capture card, like so:&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://datalars.com/includes/thumbnail.php?file=retrogaming-chart-after-2.png" /&gt;&lt;/p&gt;
&lt;p&gt;As you can see from the chart, the splitters are inserted between the first switch and the TV. This way, no latency is introduced to the TV, so we can play our games normally without any changes to our gameplay. We can also swap game consoles mid-session and have no interruptions to the video recording, since the video is captured from the first switch.&lt;/p&gt;
&lt;p&gt;Regarding the 3-6x RCA cables listed: 3 of them go from the first split of the RCA Y splitter to the TV. As for the other 3 - depending on how your splitters and AV2HDMI box look, you may or may not need them. I was able to plug the second split of the RCA Y splitter directly into the AV2HDMI box without using the extra 3 cables - your mileage may vary.&lt;/p&gt;
&lt;p&gt;The reason I recommend the HDMI capture card over the RCA/AV capture card, is because AV capture cards tend to have issues switching between NTSC and PAL video signals. I mix NTSC and PAL video signals all the time, as I own consoles and games of both regions. My CRT TV supports both signals, which isn't a given for any CRT TV either, so make sure yours does before you mix signals and end up with unusable video files. The HDMI route isn't immune to this issue either, but the AV2HDMI converter box seems to handle this a lot better, and seems to send the same signal to the capture card regardless of input. I also experienced horrible buzzing noises during recording when using my AV capture card, presumably due to interference, but that might be down to a faulty card that's been stored in a box for 10 years or more.&lt;/p&gt;
&lt;p&gt;The AV2HDMI converter box works great, and will detect a change in signal automatically. In my testing and usage, I have found only one combination of consoles/game regions that will throw it for a loop - playing NTSC games on a PAL GameCube (via Swiss). This outputs a black and white, wavy signal from the AV2HDMI box, presumably because the GameCube is outputting an esoteric (or out-of-sync) signal that it doesn't quite understand. The TV shows the signal just fine, however. Playing PAL games on the same console works fine, so I've solved this issue by just using PAL games instead. The PS2, where I also mix NTSC and PAL games on a PAL console, does not exhibit this problem, and both regions display fine when captured.&lt;/p&gt;
&lt;h2 id="post-16-software"&gt;Software&lt;/h2&gt;
&lt;p&gt;Now that all the hardware is plugged together, it's time to make this as seamless as possible using software.&lt;/p&gt;
&lt;p&gt;For the operating system, I use my default go-to for projects such as these: The latest Kubuntu LTS. KDE is my favourite desktop environment, I'm very familiar with it at this point, and the Ubuntu LTS base provides a solid foundation.&lt;/p&gt;
&lt;p&gt;The hardware of the PC is modest, as this is an ancient PC that I used as a daily driver 10 or so years ago, only upgraded with an SSD:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CPU: AMD FX-6100 Six-core @ 3.300GHz&lt;/li&gt;
&lt;li&gt;GPU: AMD ATI Radeon HD 7870 GHz Edition&lt;/li&gt;
&lt;li&gt;RAM: 8 GB&lt;/li&gt;
&lt;li&gt;SSD: 128 GB&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After installation, we can start installing some software, and setting some desired options.&lt;/p&gt;
&lt;p&gt;I've installed the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.nomachine.com/"&gt;NoMachine&lt;/a&gt; - for remote access from my main PC&lt;/li&gt;
&lt;li&gt;&lt;a href="https://obsproject.com/"&gt;OBS Studio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pschmitt/obs-cli"&gt;obs-cli&lt;/a&gt; - Make sure you get you get the one from &lt;code&gt;pschmitt&lt;/code&gt; - the other ones I've found are all defective or outdated&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, set up your scene in OBS. At the bottom, add a new video source.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://datalars.com/includes/thumbnail.php?file=2023-09-2617-57.png" /&gt;&lt;/p&gt;
&lt;p&gt;I had to pick YU12 to get the correct image for my setup, but yours may be different. The AV2HDMI converter box outputs a 720p or 1080p signal, configurable by a physical switch on the unit itself. I set mine to 720p, and the recording resolution to 576p, as no console in my setup outputs anything higher than that over AV anyway.&lt;/p&gt;
&lt;p&gt;Now that that's out of the way, we just need to configure a few things about the recording environment. In OBS, go to &lt;strong&gt;File &amp;gt; Settings&lt;/strong&gt;, then &lt;strong&gt;Output&lt;/strong&gt;, and set &lt;strong&gt;Output Mode&lt;/strong&gt; to &lt;strong&gt;Advanced&lt;/strong&gt;. Click the &lt;strong&gt;Recording&lt;/strong&gt; tab, then set your desired settings:&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://datalars.com/includes/thumbnail.php?file=2023-09-2618-11.png" /&gt;&lt;/p&gt;
&lt;p&gt;My changes from the default:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I've used &lt;code&gt;/opt/retrocord&lt;/code&gt; as the folder to store all my recordings.&lt;/li&gt;
&lt;li&gt;I've checked &amp;quot;Generate File Name without Space&amp;quot;&lt;/li&gt;
&lt;li&gt;I've set the video encoder to x264 as it is light on CPU and produces files of a manageable size&lt;/li&gt;
&lt;li&gt;I've enabled &amp;quot;Automatic File Splitting&amp;quot;&lt;/li&gt;
&lt;li&gt;I've set &amp;quot;Split Time&amp;quot; to 5 min&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The reason I've enabled automatic file splitting is twofold: To not end up with files of gargantuan sizes, and to let me delete older files in the directory when it becomes too large, without having to split file and review what I want to save.&lt;/p&gt;
&lt;p&gt;Click OK. Next, choose &lt;strong&gt;Tools&lt;/strong&gt; in the menu bar, then click &lt;strong&gt;WebSocket Server Settings&lt;/strong&gt;. Check &lt;strong&gt;Enable WebSocket server&lt;/strong&gt;, then click &lt;strong&gt;Show Connect Info&lt;/strong&gt;. Note down the &lt;strong&gt;Server Port&lt;/strong&gt; and the &lt;strong&gt;Server Password&lt;/strong&gt;, as you'll be needing it soon. Close the connection info window and click OK in the WebSocket Server window.&lt;/p&gt;
&lt;h2 id="post-16-scripting-scripts"&gt;Scripting scripts&lt;/h2&gt;
&lt;p&gt;Your recording environment should now be ready to use. The next step is to automate this, so you don't need to manually interact with OBS to make it do its thing. I accomplish this using two scripts; a login script and a logout script.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/opt/scripts/retrocorder-start.sh&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env bash
obs --startrecording &amp;amp;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;/opt/scripts/retrocorder-stop.sh&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env bash
obs-cli --host localhost --port 4455 --password abcde12345 record stop
sleep 1
kill -s TERM &amp;quot;$(pidof obs)&amp;quot;
sync

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In &lt;code&gt;retrocorder-stop.sh&lt;/code&gt;, you need to change two values in the second line: the port and the password that you noted down earlier (&lt;strong&gt;4455&lt;/strong&gt; and &lt;strong&gt;abcde12345&lt;/strong&gt; in the example above). The reason we need &lt;code&gt;obs-cli&lt;/code&gt; in the first place and the OBS WebSocket Server to be running, is that while OBS can let you send it an argument to start recording, it has no such argument to stop the recording in progress (for some god-awful reason).&lt;/p&gt;
&lt;p&gt;As you might have noticed, this setup might end up with a full disk after enough usage, so we're gonna have to deal with that with another script. This time, we're gonna set up a cron job to be run once an hour, to prune the oldest videos in the recording directory, once a size threshold has been exceeded:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/opt/scripts/retrocorder-prune.sh&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env bash
cd /opt/retrocord || exit
limitBytes=$((50*1024*1024*1024)) # 50 GiB

currentDirSize=&amp;quot;$(du -bs | awk '{print $1}')&amp;quot;
if [[ &amp;quot;$currentDirSize&amp;quot; -gt &amp;quot;$limitBytes&amp;quot; ]]; then
    while [[ &amp;quot;$currentDirSize&amp;quot; -gt &amp;quot;$limitBytes&amp;quot; ]]; do
    purgeFile=$(ls -rt *.mkv | head -n 1)
    rm &amp;quot;$purgeFile&amp;quot;
    currentDirSize=&amp;quot;$(du -bs | awk '{print $1}')&amp;quot;
    done
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This script deletes the oldest files in a directory until the total directory size is less than 50 GiB. Change the second line of the script to point to your recording directory, and the third line to reflect how much disk space you'd like to allocate to recordings. I've set mine to 50 GiB, as that is plenty, and leaves lots of headroom on the 128 GB SSD.&lt;/p&gt;
&lt;p&gt;On my setup, 5 mins of recording equals around 100 MiB. This means that I can record &amp;gt; 42 hours of gameplay before the script starts purging - more than enough time to save any clips I want to keep!&lt;/p&gt;
&lt;details&gt;
Quick maffs
50 GiB * 1024 MiB per GiB = 512,000 MiB allocated&lt;br /&gt;
512,000 MiB / 100 MiB per 5 mins = 512 files á 100 MiB&lt;br /&gt;
512 files * 5 minutes per file = 2560 minutes&lt;br /&gt;
2560 minutes ≈ 42.66 hours ≈ 1.77 days
&lt;/details&gt;
&amp;nbsp;
&lt;p&gt;Finally, put the script in your crontab. First, edit your crontab:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ crontab -e
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then append a new line at the end of the file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0 * * * * /opt/scripts/retrocorder-prune.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save and close the file (if the editor is &lt;code&gt;nano&lt;/code&gt;, press, Ctrl+X, then Y, then Enter).&lt;/p&gt;
&lt;p&gt;This will make the script run once an hour.&lt;/p&gt;
&lt;h2 id="post-16-setting-settings"&gt;Setting settings&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: These settings are for KDE Plasma. There are most likely equivalents for these settings in all other major DEs.&lt;/p&gt;
&lt;p&gt;Once the above scripts are created, in Plasma, go to &lt;strong&gt;System Settings &amp;gt; Startup and Shutdown &amp;gt; Autostart&lt;/strong&gt;. Click &lt;strong&gt;Add&lt;/strong&gt; at the bottom of the window, then pick &lt;strong&gt;Add Login Script&lt;/strong&gt;. Navigate to and pick &lt;code&gt;/opt/script/retrocorder-start.sh&lt;/code&gt;. Now do the same for the logout script: Click &lt;strong&gt;Add &amp;gt; Add Logout Script&lt;/strong&gt;, then navigate to and pick &lt;code&gt;/opt/scripts/retrocorder-stop.sh&lt;/code&gt;. This will automatically start and stop recording when you log in and out of the computer.&lt;/p&gt;
&lt;p&gt;To make this completely automatic, you'll also need to make sure you're automatically logged in to the computer. Also in &lt;strong&gt;Startup and Shutdown&lt;/strong&gt;, pick &lt;strong&gt;Login Screen (SDDM)&lt;/strong&gt;, then click the button labeled &lt;strong&gt;Behaviour&lt;/strong&gt; on the bottom left. Next, check the box next to &lt;strong&gt;Automatic log in&lt;/strong&gt;, then choose your user and session on the same line. Click &lt;strong&gt;Apply&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Also in &lt;strong&gt;Startup and Shutdown&lt;/strong&gt;, pick &lt;strong&gt;Desktop Session&lt;/strong&gt;, then uncheck the box next to &lt;strong&gt;Logout Screen - Show&lt;/strong&gt;. This makes sure when you request a shutdown, it is done immediately.&lt;/p&gt;
&lt;p&gt;The next destination is still in &lt;strong&gt;System Settings&lt;/strong&gt;, under &lt;strong&gt;Power Management&lt;/strong&gt; this time (called &lt;strong&gt;Energy Saving&lt;/strong&gt; in earlier versions of Plasma). Uncheck all checkboxes, then check &lt;strong&gt;Button events handling&lt;/strong&gt;. In the drop-down box &lt;strong&gt;When power button pressed&lt;/strong&gt;, pick &lt;strong&gt;Shut down&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Lastly, if you wish access this computer over SSH, install and enable &lt;code&gt;openssh-server&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo apt update
$ sudo apt install openssh-server
$ sudo ufw allow ssh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This allows you to log in remotely via SSH. Additionally, it lets you use &lt;a href="https://en.wikipedia.org/wiki/Files_transferred_over_shell_protocol"&gt;FISH&lt;/a&gt; to easily copy files from the Retrocorder to your main machine; SSH-enabled servers can be accessed in Dolphin by using the &lt;code&gt;fish:&lt;/code&gt; URI scheme in the address bar:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fish://192.168.0.123/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You could also set up an NFS or SMB share, but that's out of scope for this post.&lt;/p&gt;
&lt;h2 id="post-16-headless-chicken"&gt;Headless chicken&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: This section mostly applies to desktop PCs. If you're using a laptop, you're more or less done.&lt;/p&gt;
&lt;p&gt;At this point you should be in a state where everything is automatic. Starting with the PC off, when you press the power button, the computer will boot, log you in, start OBS and start recording. Once an hour, your recording directory will be checked, and if it's too big, the oldest files will be deleted. OBS will keep recording until you hit the power button. Once you hit the power button, OBS will stop recording, close, the disks will sync, and the computer will turn off.&lt;/p&gt;
&lt;p&gt;Wouldn't it be cool if you didn't need that pesky monitor, keyboard and mouse?&lt;/p&gt;
&lt;p&gt;In most cases, if you don't have a display attached, the computer will not boot to a graphical environment. There's two ways to fix this, either by creating a dummy display (Xorg only), or by getting a physical dummy connector which will fool your computer into thinking a display is attached. There are dummy connectors available for all sorts of display connectors, but this post will focus on the software solution, as it works great for me.&lt;/p&gt;
&lt;p&gt;This following solution only works on Xorg. I don't know if Wayland has an equivalent method of making a dummy display, but I'm sure you could find something by searching the web.&lt;/p&gt;
&lt;p&gt;Create the following file:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/usr/share/X11/xorg.conf.d/xorg.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Section &amp;quot;Device&amp;quot;
    Identifier  &amp;quot;Configured Video Device&amp;quot;
    Driver      &amp;quot;dummy&amp;quot;
EndSection

Section &amp;quot;Monitor&amp;quot;
    Identifier  &amp;quot;Configured Monitor&amp;quot;
    HorizSync 31.5-48.5
    VertRefresh 50-70
EndSection

Section &amp;quot;Screen&amp;quot;
    Identifier  &amp;quot;Default Screen&amp;quot;
    Monitor     &amp;quot;Configured Monitor&amp;quot;
    Device      &amp;quot;Configured Video Device&amp;quot;
    DefaultDepth 24
    SubSection &amp;quot;Display&amp;quot;
    Depth 24
    Modes &amp;quot;1280x720&amp;quot;
    EndSubSection
EndSection
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you're not on *ubuntu, &lt;code&gt;xorg.conf&lt;/code&gt; might live somewhere else, such as &lt;code&gt;/etx/X11/xorg.conf&lt;/code&gt;. In many cases, it doesn't exist and must be created - searching the web is your friend again here.&lt;/p&gt;
&lt;p&gt;Save this file, which configures a dummy 1280x720 display. In my experience, increasing the resolution doesn't do anything, the dummy display seems to max out at that resolution.&lt;/p&gt;
&lt;p&gt;Restart your computer, and recording should now start, even without a display attached! It will also enable you to remote control the desktop using &lt;a href="https://www.nomachine.com/"&gt;NoMachine&lt;/a&gt; or equivalent remote control software.&lt;/p&gt;
&lt;p&gt;You should now have a fully automated recording solution for your retro gaming setup! :) The only thing you need to do now is press the power button to turn the computer on and start recording, and press it again to shut down when you're done.&lt;/p&gt;
&lt;p&gt;Here's a sample of gameplay recorded from my Retrocorder. It's by no means perfect, but it's more than good enough for my purposes - saving fun or memorable bits of gameplay. This clip is me playing the 2006 PS2 game &lt;a href="https://en.wikipedia.org/wiki/Black_(video_game)"&gt;&lt;em&gt;Black&lt;/em&gt;&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;YouTube truncates this to 480p, so if you want the source file, you can get it &lt;a href="https://mega.nz/file/G9IlBY7J#KHluoaliC1IXvX7HV-7do3eN06LGX6kALmAuUeZTY20"&gt;here&lt;/a&gt;!&lt;/p&gt;
&lt;h3 id="post-16-closing-thoughts"&gt;Closing thoughts&lt;/h3&gt;
&lt;p&gt;The first time I tried booting my PC after setting up the dummy display, it would not start up, much to my annoyance. Turns out this motherboard is one of those who will halt the boot process if a keyboard isn't connected - easily fixed by changing the BIOS settings not to halt on keyboard errors.&lt;/p&gt;
&lt;p&gt;This was a fun project to set up!&lt;/p&gt;</description><author>datalars</author><pubDate>Tue, 26 Sep 2023 20:43:00 GMT</pubDate><guid isPermaLink="true">https://datalars.com/2023/09/26/retrocorder-make-an-automated-retro-gaming-recording-solution/</guid></item><item><title>Catching Mew: A Playable Game Boy Quote</title><link>https://www.mikekasberg.com/blog/2023/09/26/catching-mew-a-playable-gameboy-quote.html</link><description>&lt;p&gt;Last week I went to &lt;a href="https://thestrangeloop.com/"&gt;Strange Loop 2023&lt;/a&gt;. There were
many great sessions, but my favorite was &lt;a href="https://www.youtube.com/watch?v=z9JYOZWLMlo"&gt;Playable Quotes for Game Boy
Games&lt;/a&gt;.  Joël and Adam presented an
idea for &lt;em&gt;playable&lt;/em&gt; Game Boy “quotes” that should be (legally) shareable. (It’s
worth reading &lt;a href="https://joel.franusic.com/playable_quotes_for_game_boy"&gt;How We Made Playable Quotes for the Game
Boy&lt;/a&gt; on Joël’s Blog.)
Their solution’s both clever and elegant, and uses only a few hundred lines of
code (aside from existing emulators). The playable quote truly removes all the
unnecessary parts of the ROM, which is important for legal reasons.  Their
implementation is also practical and future-proof, embedding everything that’s
necessary to play the game in a single distributable file.  It’s also pretty
clever, using steganography to embed data into a screenshot of the game.  (This
revelation produced an outburst of applause at the live presentation.) While I
watched this presentation at Strange Loop, I couldn’t help but think that a
playable quote like this would be a great way to experience catching Mew in the
original Pokémon Game Boy games.&lt;/p&gt;

&lt;div class="message"&gt;
&lt;b&gt;Skip Ahead:&lt;/b&gt; &lt;a href="https://www.mikekasberg.com/blog/2023/09/26/catching-mew-a-playable-gameboy-quote.html#how-to-catch-mew"&gt;How to Catch Mew&lt;/a&gt;
&lt;/div&gt;

&lt;h2 id="the-legend-of-mew"&gt;The Legend of Mew&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://bulbapedia.bulbagarden.net/wiki/Mew_(Pok%C3%A9mon)"&gt;Mew&lt;/a&gt; is my favorite
Pokémon. Not because it’s a cool, rare Pokémon with special abilities that’s
hard to catch, but because of the &lt;strong&gt;incredible programming story&lt;/strong&gt; of how it
came to be. In fact, this might be my &lt;em&gt;favorite&lt;/em&gt; programming story. It’s on par
with &lt;a href="https://web.mit.edu/jemorris/humor/500-miles"&gt;We can’t send e-mail more than 500
miles&lt;/a&gt; and some of the other
wonderful stories listed &lt;a href="http://beza1e1.tuxen.de/lore/index.html"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I have fond memories of playing Pokémon as a child, but I never caught Mew.
Still, I remember all the rumors that I’d talk about with my friends. Someone
heard a rumor that you could find Mew next to a truck by the S.S. Anne after
beating the Elite Four. Another friend thought that you could only get Mew with
a GameShark.  Someone else heard you could only get Mew at official Nintendo
events where they gave it out. As a young kid (without access to Nintendo events
or a GameShark), all these rumors added to the mystery and intrigue of Mew, and
they hype around Mew probably contributed to the success of Pokémon.&lt;/p&gt;

&lt;p&gt;As it turns out, the real story behind Mew is a programming tale. I find this
story fascinating, and it makes me like Mew even more than the legends I heard
in my childhood. Mew was originally created by Shigeki Morimoto, a programmer
for Game Freak. And he snuck it into the code! It was a sort of Easter egg,
originally intended only for Game Freak staff. Morimoto added it to the game at
the last minute, after the removal of some debug features in preparation for the
final release freed up enough space to add Mew. He didn’t add any way to catch
or encounter it – I guess he meant to use his developer tools or some
GameShark-like device to let people he knew get Mew in their game. All
programmers love a good Easter egg, and this might be the most famous Easter egg
I’ve ever heard of (even if most people now know Mew as a regular Pokémon and
don’t realize it was originally just an Easter egg). It’s worth reading a full
account of the story on &lt;a href="https://en.wikipedia.org/wiki/Mew_(Pok%C3%A9mon)#Concept_and_creation"&gt;Mew’s Wikipedia
Page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Programmers love Easter eggs, but we also love interesting bugs and glitches. If
Mew was originally intended to be an unobtainable Easter egg, the fact that
&lt;strong&gt;it’s actually possible to catch a Mew in an unmodified game&lt;/strong&gt; by exploiting a
glitch makes it even more alluring! Pokémon Game Boy games use a pseudo-random
number generator (PRNG) to randomize the Pokémon you encounter. By manipulating
this PRNG, it’s possible to predictably and reliably encounter a wild Mew after
a series of specific actions! Exploiting the glitch is a little tricky to pull
off, and requires a series of specific actions at specific points in the game.
This particular glitch requires a couple hours of play time to get to the right
part of the game, and isn’t usable once you’ve passed it (by defeating the
trainers).&lt;/p&gt;

&lt;p&gt;This seems like a perfect application of the playable quotes I learned about at
Strange Loop, so I spent a couple hours over my weekend creating them!&lt;/p&gt;

&lt;h2 id="how-to-catch-mew"&gt;How to Catch Mew&lt;/h2&gt;

&lt;div class="message"&gt;
All the images below are &lt;a href="https://joel.franusic.com/playable_quotes_for_game_boy"&gt;playable Game Boy quotes&lt;/a&gt;. Click on them
to see the animation or play the game yourself!
&lt;/div&gt;

&lt;p&gt;The earliest point you can catch Mew in Pokémon Red/Blue is in Cerulean city.
You need to defeat the five trainers on Nugget bridge, but you need to be very
careful with trainer battles after that because the glitch we’re going to
exploit requires battles with a couple of the upcoming trainers.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Catch an &lt;a href="https://bulbapedia.bulbagarden.net/wiki/Abra_(Pok%C3%A9mon)"&gt;Abra&lt;/a&gt;. You can encounter a wild Abra in the grass North of Nugget
Bridge on Route 24. Abra is difficult to catch because the only move it knows is
Teleport, and it will use Teleport to escape if it gets the opportunity. The
best way to catch it is to immediately put it to sleep, perhaps with
Butterfree’s Sleep Powder.&lt;/p&gt;

    &lt;p&gt;&lt;a href="https://tenmile.quote.games/play#drop=https://www.mikekasberg.com/images/catching-mew/00-catch-abra.png"&gt;
  &lt;img alt="A playable Game Boy quote. Making Abra fall asleep." class="img-responsive img-blog-content" src="https://www.mikekasberg.com/images/catching-mew/00-catch-abra.png" /&gt;
&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Visit the Pokémon Center in Cerulean City. This is important because you’ll
teleport to the most recent Pokémon Center you visited. You should also prepare
your party at this point. You’ll want 1-2 strong Pokémon to win a trainer
battle, and perhaps a couple weaker Pokémon or Pokémon with special abilities to
use to weaken and catch Mew.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Save your game. Don’t save again until you’ve caught mew. This way you can
try again if you make a mistake.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Walk down the grassy area West of Nugget Bridge right next to the water. You
must press start quickly after moving South but before you finish moving. Your
goal is to pause the game such that when you unpause, the trainer will see you
and trigger a battle. Rather than unpausing, use Teleport. The trainer will spot
you, but you’ll Teleport away. This can be difficult to describe, so perhaps a
playable quote will help!&lt;/p&gt;

    &lt;p&gt;&lt;a href="https://tenmile.quote.games/play#drop=https://www.mikekasberg.com/images/catching-mew/01-nugget-bridge-teleport.png"&gt;
  &lt;img alt="A playable Game Boy quote. Teleporting away from the trainer battle West of Nugget Bridge." class="img-responsive img-blog-content" src="https://www.mikekasberg.com/images/catching-mew/01-nugget-bridge-teleport.png" /&gt;
&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Walk back up Nugget Bridge and battle a specific trainer on Route 24. It is important that you are
a few steps away so the trainer has to walk to you. Win the battle.&lt;/p&gt;

    &lt;p&gt;&lt;a href="https://tenmile.quote.games/play#drop=https://www.mikekasberg.com/images/catching-mew/02-beat-trainer.png"&gt;
  &lt;img alt="A playable Game Boy quote. Battling the trainer on Route 24." class="img-responsive img-blog-content" src="https://www.mikekasberg.com/images/catching-mew/02-beat-trainer.png" /&gt;
&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Immediately after the battle ends, use Teleport.&lt;/p&gt;

    &lt;p&gt;&lt;a href="https://tenmile.quote.games/play#drop=https://www.mikekasberg.com/images/catching-mew/03-teleport-2.png"&gt;
  &lt;img alt="A playable Game Boy quote. Teleporting away from the trainer on Route 24." class="img-responsive img-blog-content" src="https://www.mikekasberg.com/images/catching-mew/03-teleport-2.png" /&gt;
&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Walk back up Nugget Bridge. Upon leaving Cerulean City, the game will
glitch and the menu will come up even though you didn’t press
&lt;kbd&gt;START&lt;/kbd&gt;. Exit the menu and &lt;strong&gt;you will encounter Mew!&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;&lt;a href="https://tenmile.quote.games/play#drop=https://www.mikekasberg.com/images/catching-mew/04-catching-mew.png"&gt;
  &lt;img alt="A playable Game Boy quote. Catching Mew!" class="img-responsive img-blog-content" src="https://www.mikekasberg.com/images/catching-mew/04-catching-mew.png" /&gt;
&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I find it fascinating that you can exploit a glitch in Pokémon to catch Mew, the
uncatchable Pokémon! It brought me a lot of joy to listen to the &lt;a href="https://www.youtube.com/watch?v=z9JYOZWLMlo"&gt;Playable
Quotes for Game Boy Games&lt;/a&gt; session
at Strange Loop, it was fun to build these playable quotes, and I hope Joël and
Adam enjoy seeing their quotes in use here on my blog! I also hope that these
playable quotes help create one of the most approachable, easy-to-understand
tutorials for catching Mew. It’s worth noting that the quotes above contain a
complete save-state, and it’s possible to continue playing if you drop a
complete ROM onto the quote player. It should also be possible to export the
save-state from any quote in a widely-compatible .sav or .srm format, but doing
so is left as an exercise for the reader, for now.&lt;/p&gt;</description><author>Mike Kasberg's Blog</author><pubDate>Tue, 26 Sep 2023 16:30:00 GMT</pubDate><guid isPermaLink="true">https://www.mikekasberg.com/blog/2023/09/26/catching-mew-a-playable-gameboy-quote.html</guid></item><item><title>About me</title><link>https://www.skouf.com/about/</link><description>Hey 👋 I&amp;rsquo;m Nik, a software developer based in Vancouver, Canada. Originally from Melbourne, Australia, I moved to Canada in April of 2024.
To get a feel for the work that I do and what I like to write about, the following are some of my favourite blog posts:
Beware of transit gateways bearing large packets (on Medium) is an interesting caper involving some of the finer points of AWS Transit Gateways, asymmetric routing and MSS clamping.</description><author>skouf.com</author><pubDate>Tue, 26 Sep 2023 09:09:52 GMT</pubDate><guid isPermaLink="true">https://www.skouf.com/about/</guid></item><item><title>What is a blunder in chess?</title><link>https://blog.lukesalamone.com/posts/chess-blunders/</link><description>&lt;p&gt;What is a blunder in chess? The tension between the qualitative and quantitative answers to this question is at the heart of different approaches towards chess, and more broadly, how quantitative metrics may lack context, but qualitative metrics lack precision.&lt;/p&gt;
&lt;h2 id="qualitative-answer"&gt;Qualitative answer&lt;/h2&gt;
&lt;p&gt;There are many qualitative answers to this question, especially when comparing &amp;ldquo;blunders&amp;rdquo; and &amp;ldquo;mistakes&amp;rdquo;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;ldquo;a move that negatively affects their position in a significant way&amp;rdquo; ~ &lt;a href="https://www.chess.com/terms/chess-blunder"&gt;chess.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;severely worsens the player&amp;rsquo;s situation by allowing a loss of material, checkmate, or anything similar&amp;rdquo; ~ &lt;a href="https://en.wikipedia.org/wiki/Blunder_%28chess%29"&gt;Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Blunders tend to be immediately refutable, while mistakes require planning to capitalize on.&amp;rdquo; ~ &lt;a href="https://www.reddit.com/r/chess/comments/1iiqyb/what_distinguishes_the_difference_between_a/"&gt;r/chess&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;An issue with these qualitative answers is that while their words may be correct, smart people may still disagree with their applicability at the margins. For a suboptimal move to have a &amp;ldquo;significant&amp;rdquo; negative effect, it requires that the opponent notices and takes advantage of it.&lt;/p&gt;</description><author>Luke Salamone's Blog</author><pubDate>Tue, 26 Sep 2023 07:47:30 GMT</pubDate><guid isPermaLink="true">https://blog.lukesalamone.com/posts/chess-blunders/</guid></item><item><title>How (not) to apply for a software job</title><link>https://benhoyt.com/writings/how-to-apply/</link><description>Advice for how to (and how not to) apply for a software engineering job, particularly for the written parts of the interview process. As a bonus, some tips for your resume/CV.</description><author>Ben Hoyt's technical writing</author><pubDate>Tue, 26 Sep 2023 07:30:00 GMT</pubDate><guid isPermaLink="true">https://benhoyt.com/writings/how-to-apply/</guid></item><item><title>Blacked out lens markings</title><link>https://voussoir.net/writing/blacked_out_lens_markings</link><description>&lt;article&gt;
&lt;p&gt;&lt;a href="/writing"&gt;Back to writing&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a class="tag_link" href="/writing/tags/photography"&gt;[photography]&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="tag_link" href="/writing/tags/today_i_did_this"&gt;[today_i_did_this]&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Blacked out lens markings&lt;/h1&gt;
&lt;p&gt;My camera lenses have white lettering printed and etched into the front describing their properties. I decided to paint over it.&lt;/p&gt;
&lt;p&gt;Why:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;I like stuff that is debadged or debranded and I think it will look cool.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I think / assume that the white lettering is distracting and catches attention, which is bad for candids.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I don't need my lens to remind me what it is. &lt;a href="https://www.youtube.com/watch?v=WMO0mPb-DlM" title="Pulp Fiction - I don't need you to tell me how good my coffee is"&gt;I'm the one who bought it&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I don't need my lens to tell everyone else what it is. If they care, they'll ask. If they don't, I'll spare them the advertisement.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I have no intention of ever selling them.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I used some matte black enamel paint and a 2mm round brush. I got the best results by picking up &lt;strong&gt;very&lt;/strong&gt; light amounts of paint and dabbing it on. Any attempt to stroke the brush or speed up the process resulted in a horrible texture. I did a few coats on each lens. If you are a chemist accustomed to handling individual molecules of substance at a time, you might have more patience for this. It doesn't look factory-perfect, but I am satisfied with the look and texture I got in the end.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://files.voussoir.net/writing/blacked_out_lens_markings/27_before.jpg"&gt;&lt;img alt="" src="https://files.voussoir.net/writing/blacked_out_lens_markings/small_27_before.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://files.voussoir.net/writing/blacked_out_lens_markings/27_after.jpg"&gt;&lt;img alt="" src="https://files.voussoir.net/writing/blacked_out_lens_markings/small_27_after.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://files.voussoir.net/writing/blacked_out_lens_markings/1855_before.jpg"&gt;&lt;img alt="" src="https://files.voussoir.net/writing/blacked_out_lens_markings/small_1855_before.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://files.voussoir.net/writing/blacked_out_lens_markings/1855_after.jpg"&gt;&lt;img alt="" src="https://files.voussoir.net/writing/blacked_out_lens_markings/small_1855_after.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://files.voussoir.net/writing/blacked_out_lens_markings/55200_before.jpg"&gt;&lt;img alt="" src="https://files.voussoir.net/writing/blacked_out_lens_markings/small_55200_before.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://files.voussoir.net/writing/blacked_out_lens_markings/55200_after.jpg"&gt;&lt;img alt="" src="https://files.voussoir.net/writing/blacked_out_lens_markings/small_55200_after.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://files.voussoir.net/writing/blacked_out_lens_markings/glamour.jpg"&gt;&lt;img alt="" src="https://files.voussoir.net/writing/blacked_out_lens_markings/small_glamour.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;
&lt;p&gt;&lt;a href="https://git.voussoir.net/voussoir/voussoir.net/commits/master/voussoir.net/writing/blacked_out_lens_markings/blacked_out_lens_markings.md"&gt;View this document's history&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://git.voussoir.net/voussoir/voussoir.net/commit/25fcaa458034edaf1fcf9a918b655bcf7fb966ee"&gt;&lt;time datetime="2023-09-25T19:02:58-07:00"&gt;2023-09-25&lt;/time&gt; Add blacked_out_lens_markings.md.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;address&gt;Contact me: writing@voussoir.net&lt;/address&gt;
&lt;p&gt;If you would like to subscribe for more, add this to your RSS reader: &lt;a href="/writing/writing.atom" rel="alternate" type="application/atom+xml"&gt;https://voussoir.net/writing/writing.atom&lt;/a&gt;&lt;/p&gt;
&lt;/article&gt;</description><author>voussoir.net/writing</author><pubDate>Tue, 26 Sep 2023 05:02:58 GMT</pubDate><guid isPermaLink="true">https://voussoir.net/writing/blacked_out_lens_markings</guid></item><item><title>Add Custom Command</title><link>https://studiofreya.org/cmake/add-custom-command/</link><description>&lt;p&gt;Add custom command in CMake can be used to copy assets and runtime dependencies into the build folder for easier debugging.&lt;/p&gt;
&lt;p&gt;The one thing to remember when you&amp;rsquo;re about to copy assets/dependencies into the build folder, is that it should be done at the &lt;em&gt;BUILD&lt;/em&gt; stage, and not the &lt;em&gt;CONFIGURE&lt;/em&gt; stage of CMake.&lt;/p&gt;
&lt;p&gt;If you find your self doing generator expressions during the configure stage of CMake, you&amp;rsquo;re doing something wrong.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-cmake"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #75715e;"&gt;# Wrong
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;configure_file(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;${&lt;/span&gt;CMAKE_SOURCE_DIR&lt;span style="color: #f92672;"&gt;}&lt;/span&gt;&lt;span style="color: #e6db74;"&gt;/bin/runtime-lib.dll&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;${&lt;/span&gt;CMAKE_BINARY_DIR&lt;span style="color: #f92672;"&gt;}&lt;/span&gt;&lt;span style="color: #e6db74;"&gt;/&lt;/span&gt;&lt;span style="color: #f92672;"&gt;$&amp;lt;&lt;/span&gt;CONFIG&lt;span style="color: #f92672;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #e6db74;"&gt;/&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #75715e;"&gt;#             error ^^^^^^^^^
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #e6db74;"&gt;COPYONLY&lt;/span&gt;)&lt;span style="color: #960050; background-color: #1e0010;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This generator expression will not be executed, since this is evaluated during configure. Generator expressions are only evaluated during build.&lt;/p&gt;
&lt;p&gt;Use &lt;code&gt;add_custom_command&lt;/code&gt; to copy over runtime dependencies to the build folder.&lt;/p&gt;
&lt;p&gt;Given the following &lt;code&gt;CMakeLists.txt&lt;/code&gt;;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div&gt;
&lt;table style="border-spacing: 0; padding: 0; margin: 0; border: 0;"&gt;&lt;tr&gt;&lt;td style="vertical-align: top; padding: 0; margin: 0; border: 0;"&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&lt;span&gt; 1
&lt;/span&gt;&lt;span&gt; 2
&lt;/span&gt;&lt;span&gt; 3
&lt;/span&gt;&lt;span&gt; 4
&lt;/span&gt;&lt;span&gt; 5
&lt;/span&gt;&lt;span&gt; 6
&lt;/span&gt;&lt;span&gt; 7
&lt;/span&gt;&lt;span&gt; 8
&lt;/span&gt;&lt;span style="background-color: #3c3d38;"&gt;&lt;span&gt; 9
&lt;/span&gt;&lt;/span&gt;&lt;span style="background-color: #3c3d38;"&gt;&lt;span&gt;10
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;11
&lt;/span&gt;&lt;span style="background-color: #3c3d38;"&gt;&lt;span&gt;12
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-cmake"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;cmake_minimum_required(&lt;span style="color: #e6db74;"&gt;VERSION&lt;/span&gt; &lt;span style="color: #e6db74;"&gt;3.27.0&lt;/span&gt;)&lt;span style="color: #960050; background-color: #1e0010;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;set(&lt;span style="color: #e6db74;"&gt;CMAKE_CXX_STANDARD&lt;/span&gt; &lt;span style="color: #e6db74;"&gt;23&lt;/span&gt;)&lt;span style="color: #960050; background-color: #1e0010;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;set(&lt;span style="color: #e6db74;"&gt;CMAKE_CXX_STANDARD_REQUIRED&lt;/span&gt; &lt;span style="color: #e6db74;"&gt;ON&lt;/span&gt;)&lt;span style="color: #960050; background-color: #1e0010;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;set(&lt;span style="color: #e6db74;"&gt;CMAKE_RUN&lt;/span&gt;)&lt;span style="color: #960050; background-color: #1e0010;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #960050; background-color: #1e0010;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;project(&lt;span style="color: #e6db74;"&gt;add-custom-command&lt;/span&gt;)&lt;span style="color: #960050; background-color: #1e0010;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;add_executable(&lt;span style="color: #e6db74;"&gt;hello&lt;/span&gt; &lt;span style="color: #e6db74;"&gt;add-custom-command.cpp&lt;/span&gt;)&lt;span style="color: #960050; background-color: #1e0010;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #960050; background-color: #1e0010;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex; background-color: #3c3d38;"&gt;&lt;span&gt;add_custom_command(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex; background-color: #3c3d38;"&gt;&lt;span&gt;    &lt;span style="color: #e6db74;"&gt;TARGET&lt;/span&gt; &lt;span style="color: #e6db74;"&gt;hello&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #e6db74;"&gt;COMMAND&lt;/span&gt; &lt;span style="color: #f92672;"&gt;${&lt;/span&gt;CMAKE_COMMAND&lt;span style="color: #f92672;"&gt;}&lt;/span&gt; &lt;span style="color: #e6db74;"&gt;-E&lt;/span&gt; &lt;span style="color: #e6db74;"&gt;copy_if_different&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex; background-color: #3c3d38;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;${&lt;/span&gt;CMAKE_SOURCE_DIR&lt;span style="color: #f92672;"&gt;}&lt;/span&gt;&lt;span style="color: #e6db74;"&gt;/dependency.txt&lt;/span&gt; &lt;span style="color: #f92672;"&gt;${&lt;/span&gt;CMAKE_BINARY_DIR&lt;span style="color: #f92672;"&gt;}&lt;/span&gt;&lt;span style="color: #e6db74;"&gt;/&lt;/span&gt;&lt;span style="color: #f92672;"&gt;$&amp;lt;&lt;/span&gt;CONFIG&lt;span style="color: #f92672;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #e6db74;"&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Build with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-bash"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;cmake -S. -Bbuild
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><author>Studiofreya SSG Site</author><pubDate>Tue, 26 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://studiofreya.org/cmake/add-custom-command/</guid></item><item><title>Fort Bragg to Weaverville</title><link>https://ellie.wtf/life/ft-bragg-to-weaverville/</link><description>&lt;p&gt;&lt;img alt="" src="https://img.ellie.wtf/i/e5e5abad2aeaf60297dc2a9f0f50a182c7339fe7a5bdaa2ee41808e859093d68.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Carrying on from &lt;a href="https://ellie.wtf/life/san-francisco-to-fort-bragg/"&gt;yesterday&lt;/a&gt;, we woke up early and got ready to ride the stretch up to Weaverville. The view from the hotel this morning was absolutely stunning - the fog was slowly burning off as the sun was brightening up, and the valley was echoing with seal calls. They were less atmospheric at 3am last night, though.&lt;/p&gt;
&lt;p&gt;I've entirely shaken off the jet lag at this point, and slept through to 06:30 without any major difficulties 🥳 It only took two nights to get used to the new timezone, so not bad!&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://img.ellie.wtf/i/33da2adfba0dd3bd0ec0023caa9c3c84770166f4beb0ecc7b368a08362fb6cda.jpeg" /&gt;&lt;/p&gt;
&lt;p&gt;Despite my best efforts, my gear was still very soggy from being caught in the rain last night - and the tiny hotel hair dryer didn’t do much to help. Luckily later in the day was nice and sunny so it dried out pretty fast&lt;/p&gt;
&lt;p&gt;We stopped for breakfast at a cute coffee shop - I had waffles 😋 There was an awful lot of fog this morning, and at times I couldn’t see far at all. It only really abated when we turned inland and started to head away from the coast&lt;/p&gt;
&lt;img class="tall-img" src="https://img.ellie.wtf/i/aeec219bed70d75ab790246f95bec52a8e4b9f7f8ba51b06c587dc2726d83933.jpeg" /&gt;
&lt;p&gt;After turning off, we spent a good while riding through forest. There was no fog, but it was dense enough and the road dirty enough that I was being super careful not to slide out.&lt;/p&gt;
&lt;p&gt;Some of the redwoods are impressively large, the biggest I saw was at a tourist spot. Turns out, in America, they even have drive-thru trees!&lt;/p&gt;
&lt;img class="tall-img" src="https://img.ellie.wtf/i/b234bbf77cc36f6c63ce8855a8a24f6b59fce6fdc143670e680f90d7d368d407.jpeg" /&gt;
&lt;p&gt;Our next stop was Garberville, and it was the last one before many miles without petrol stations. We filled up with “gas”, and snacks. It was at this point that Michelle’s key snapped out of the holder, so we also had to pick up some pliars&lt;/p&gt;
&lt;p&gt;The roads from this point were even better - endless mountains and views! Cows were free roaming (and would just sit in the road), and I lost count of how many deer we saw. They’re very nervous around bikes, so it’s best to slow right down&lt;/p&gt;
&lt;p&gt;We turned onto Highway 36, which was fantastic fun. It was very smooth, and the right mix of twisty and fast. We also saw a very large number of Porsches, which seem to be the vehicle of choice around there if you’re not driving a giant truck.&lt;/p&gt;
&lt;p&gt;We were aiming to have lunch at the Mad River Burger Bar, but it was closed 😞 I was very glad I had emergency snacks from earlier! The bar next door was open, and I left a signed dollar&lt;/p&gt;
&lt;div class="img-pair"&gt;
&lt;img class="tall-img" src="https://img.ellie.wtf/i/ad7dd73b5a79300d43169c47a19d3ac7fef02f64b26f9b788472ac2e54c4b2b8.jpeg" /&gt;
&lt;img class="tall-img" src="https://img.ellie.wtf/i/a8fc4d438dd9d9d3ddd1bc5ae45385ba37a1e7407bb932f5cc69bbe27b97871a.jpeg" /&gt;
&lt;/div&gt;
&lt;p&gt;The final stretch to the Airbnb was all twisty roads, and highway 3 among the best of them so far. It’s starting to feel like we’re properly in the countryside now, further away from the bigger cities.&lt;/p&gt;
&lt;p&gt;I'd seen a lot of wild fire warnings on google maps, and a lot of signs saying that the fire risk today was "moderate". At one point, we were riding through an area that had been burned probably a few years back. It was sad to see the blackened skeletons of thousands of trees, especially knowing that this happens every year&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://img.ellie.wtf/i/2ea431cfbcbd088aab0494a04535e864365237d71a3790005ae8c69738b6a8d5.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;The Airbnb we're staying in is a little outside of Weaverville, so it's peaceful and quiet. It's a cabin in the forest, with a nice outside seating area&lt;/p&gt;
&lt;img class="tall-img" src="https://img.ellie.wtf/i/1d875369b4b02669a077439c778531937f996b5c769c8ebfa64ee515ebb810cc.jpg" /&gt;
&lt;p&gt;We will be staying here and exploring for a few days, so picked up some groceries. Some learnings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hard cider is alcoholic, normal cider might not be but isn’t just apple juice?&lt;/li&gt;
&lt;li&gt;it’s pronounced VITE-A-MINS&lt;/li&gt;
&lt;li&gt;Everything is bigger than back home - other than the bread, which was tiny&lt;/li&gt;
&lt;li&gt;A whole section in the supermarket for cookie dough!&lt;/li&gt;
&lt;li&gt;During fire season you’re not even allowed an indoor fire in the stove&lt;/li&gt;
&lt;li&gt;$10.99 for a tiny box of PG tips!!!! Before tax. I should start a business importing tea&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As the sun was going down, we walked down the road towards the river&lt;/p&gt;
&lt;div class="img-pair"&gt;
&lt;img src="https://img.ellie.wtf/i/0f71907c18cbd404f45b5fd70c02c0dec24588bb4cc0f39667d0a330d8b6fbe1.jpg" /&gt;
&lt;img src="https://img.ellie.wtf/i/596a84f42c35dcdb6df2b9edab54109c0d198f632dc94a81758b4c9540a2b7e8.jpg" /&gt;
&lt;/div&gt;
&lt;p&gt;I'm super super tired while writing this, so I imagine I'll be getting an early night tonight 😴 We covered 205 miles today!&lt;/p&gt;</description><author>Ellie's Notes</author><pubDate>Tue, 26 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ellie.wtf/life/ft-bragg-to-weaverville/</guid></item><item><title>Circles</title><link>http://localhost:4000/2023/09/26/circles.html</link><description>Circles</description><author>ms75</author><pubDate>Tue, 26 Sep 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">http://localhost:4000/2023/09/26/circles.html</guid></item><item><title>Grandpa's guide to autoresizingMask for modern UIKit developers</title><link>https://whackylabs.com/swift/uikit/layout/2023/09/25/autoresizing-mask/</link><description>&lt;p&gt;So I wanted to write a quick tutorial on &lt;code class="language-plaintext highlighter-rouge"&gt;autoresizingMask&lt;/code&gt;. It’s quick because there isn’t a lot of talk about. Or is it?&lt;/p&gt;

&lt;p&gt;Before constraints based layout became a thing there was frame based layout. You were supposed to do the math yourself and just pass in the &lt;code class="language-plaintext highlighter-rouge"&gt;frame&lt;/code&gt; property of the &lt;code class="language-plaintext highlighter-rouge"&gt;UIView&lt;/code&gt; to UIKit. And that was all there to it. Later if you wanted to support both landscape and portrait mode you could use &lt;code class="language-plaintext highlighter-rouge"&gt;autoresizingMask&lt;/code&gt; to avoid repeating the math.&lt;/p&gt;

&lt;p&gt;The &lt;code class="language-plaintext highlighter-rouge"&gt;autoresizingMask&lt;/code&gt; is just an bunch of flags that you can mix together with the bitwise &lt;code class="language-plaintext highlighter-rouge"&gt;|&lt;/code&gt; operator.&lt;/p&gt;

&lt;div class="language-objc highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="nf"&gt;NS_OPTIONS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NSUInteger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UIViewAutoresizing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;UIViewAutoresizingNone&lt;/span&gt;                 &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;UIViewAutoresizingFlexibleLeftMargin&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;UIViewAutoresizingFlexibleWidth&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;UIViewAutoresizingFlexibleRightMargin&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;UIViewAutoresizingFlexibleTopMargin&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;UIViewAutoresizingFlexibleHeight&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;UIViewAutoresizingFlexibleBottomMargin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;UIViewAutoresizing&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UIViewAutoresizingFlexibleWidth&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;UIViewAutoresizingFlexibleHeight&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In Swift, this gets exported as an &lt;code class="language-plaintext highlighter-rouge"&gt;OptionSet&lt;/code&gt;, that you can then use with the fancy array syntax.&lt;/p&gt;
&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;AutoresizingMask&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;OptionSet&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;flexibleLeftMargin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;AutoresizingMask&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;flexibleWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;AutoresizingMask&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;flexibleRightMargin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;AutoresizingMask&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;flexibleTopMargin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;AutoresizingMask&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;flexibleHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;AutoresizingMask&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;flexibleBottomMargin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;AutoresizingMask&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIViewAutoresizing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flexibleWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flexibleHeight&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So what can you do with this mask thing? Not a lot actually. Remember you were supposed to do all the math yourself, and this was just a quick way to make sure your same layout works even when the orientation changes, if that makes sense. So to do such a math you would initially calculate the &lt;code class="language-plaintext highlighter-rouge"&gt;frame&lt;/code&gt; of a view based on the geometry of the &lt;code class="language-plaintext highlighter-rouge"&gt;superview&lt;/code&gt;. So whenever the orientation would change it would simply mean the outmost frame (&lt;code class="language-plaintext highlighter-rouge"&gt;UIWindow&lt;/code&gt; in this case) &lt;code class="language-plaintext highlighter-rouge"&gt;width&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;height&lt;/code&gt; values got swapped. So every subview would then adjust their frame accordingly. But in some cases you’d prefer to recalculate the &lt;code class="language-plaintext highlighter-rouge"&gt;frame&lt;/code&gt; based on the margin from the &lt;code class="language-plaintext highlighter-rouge"&gt;superview&lt;/code&gt; - &lt;code class="language-plaintext highlighter-rouge"&gt;autoresizingMask&lt;/code&gt; was the way to avoid doing that recalculation.&lt;/p&gt;

&lt;p&gt;Conceptually &lt;code class="language-plaintext highlighter-rouge"&gt;autoresizingMask&lt;/code&gt; works with the idea of breaking down the layout in 3 components per direction. So &lt;code class="language-plaintext highlighter-rouge"&gt;[left, width, right]&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;[top, height, bottom]&lt;/code&gt;. And using &lt;code class="language-plaintext highlighter-rouge"&gt;autoresizingMask&lt;/code&gt; we could specify what components don’t have a fixed value.&lt;/p&gt;

&lt;p&gt;To illustrate, let’s say we know we want a view with some exact offset values from the edges, we could build it like:&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;bgFrame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bounds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insetBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;dy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;bgVw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UIView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bgFrame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;bgVw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;backgroundColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;systemGray&lt;/span&gt;
&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSubview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bgVw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="bg-portrait-good.png" src="https://whackylabs.com/assets/autoresizingmask/bg-portrait-good.png" /&gt;&lt;/p&gt;

&lt;p&gt;So far so good. But as soon as we rotate the device our view starts getting drawn outside of the device frame.&lt;/p&gt;

&lt;p&gt;&lt;img alt="bg-landscape-bad.png" src="https://whackylabs.com/assets/autoresizingmask/bg-landscape-bad.png" /&gt;&lt;/p&gt;

&lt;p&gt;The fix for this is to set the &lt;code class="language-plaintext highlighter-rouge"&gt;autoresizingMask&lt;/code&gt; with flexible &lt;code class="language-plaintext highlighter-rouge"&gt;width&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;height&lt;/code&gt; to indicate that all the margins are fixed and so the width and height are flexible:&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="n"&gt;bgVw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;autoresizingMask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flexibleWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flexibleHeight&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="bg-landscape-good.png" src="https://whackylabs.com/assets/autoresizingmask/bg-landscape-good.png" /&gt;&lt;/p&gt;

&lt;p&gt;Next, if we wanted to have 4 subviews all pinned to the corners of the &lt;code class="language-plaintext highlighter-rouge"&gt;bgVw&lt;/code&gt;, first we would have to do the math and then set the frames to the respective subviews.&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;bgFrame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bounds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insetBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;dy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;cornerSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CGSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;topLeftFrame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CGRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bgFrame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bgFrame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minY&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt; 
  &lt;span class="nv"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cornerSize&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;topRightFrame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CGRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bgFrame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;maxX&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;cornerSize&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bgFrame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minY&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt; 
  &lt;span class="nv"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cornerSize&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;bottomLeftFrame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CGRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bgFrame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bgFrame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;maxY&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;cornerSize&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt; 
  &lt;span class="nv"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cornerSize&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;bottomRightFrame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CGRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bgFrame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;maxX&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;cornerSize&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bgFrame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;maxY&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;cornerSize&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt; 
  &lt;span class="nv"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cornerSize&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
 
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;topLeftVw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UIView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;topLeftFrame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;topLeftVw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;backgroundColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;systemTeal&lt;/span&gt;
&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSubview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topLeftVw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;topRightVw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UIView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;topRightFrame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;topRightVw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;backgroundColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;systemYellow&lt;/span&gt;
&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSubview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topRightVw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;bottomLeftVw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UIView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bottomLeftFrame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;bottomLeftVw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;backgroundColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;systemPink&lt;/span&gt;
&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSubview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bottomLeftVw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;bottomRightVw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UIView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bottomRightFrame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;bottomRightVw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;backgroundColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;systemIndigo&lt;/span&gt;
&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSubview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bottomRightVw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And ta-da!&lt;/p&gt;

&lt;p&gt;&lt;img alt="corners-portrait-good.png" src="https://whackylabs.com/assets/autoresizingmask/corners-portrait-good.png" /&gt;&lt;/p&gt;

&lt;p&gt;But then as soon as we tilt our device (or in my case the simulator), the UI is all weird looking&lt;/p&gt;

&lt;p&gt;&lt;img alt="corners-landscape-bad.png" src="https://whackylabs.com/assets/autoresizingmask/corners-landscape-bad.png" /&gt;&lt;/p&gt;

&lt;p&gt;But not to worry, the &lt;code class="language-plaintext highlighter-rouge"&gt;autoresizingMask&lt;/code&gt; will save our day. This time we know that the &lt;code class="language-plaintext highlighter-rouge"&gt;size&lt;/code&gt; is fixed but the margins are flexible. So for example the view at top-left corner needs to have the &lt;code class="language-plaintext highlighter-rouge"&gt;left&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;top&lt;/code&gt; margins fixed or in other words the &lt;code class="language-plaintext highlighter-rouge"&gt;right&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;bottom&lt;/code&gt; margins are flexible. And similarly for all other corners:&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="n"&gt;topLeftVw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;autoresizingMask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flexibleRightMargin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flexibleBottomMargin&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;topRightVw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;autoresizingMask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flexibleLeftMargin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flexibleBottomMargin&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;bottomLeftVw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;autoresizingMask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flexibleRightMargin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flexibleTopMargin&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;bottomRightVw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;autoresizingMask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flexibleLeftMargin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flexibleTopMargin&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And boom! We are all good again!&lt;/p&gt;

&lt;p&gt;&lt;img alt="corners-landscape-good.png" src="https://whackylabs.com/assets/autoresizingmask/corners-landscape-good.png" /&gt;&lt;/p&gt;

&lt;p&gt;Next what if we wanted a subview that is always centered? The first part is again to just do the math&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;centerFrame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CGRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bgFrame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;midX&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cornerSize&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bgFrame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;midY&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cornerSize&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cornerSize&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;cornerSize&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;centerVw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UIView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;centerFrame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;centerVw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;backgroundColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;black&lt;/span&gt;
&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSubview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;centerVw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="center-portrait" src="https://whackylabs.com/assets/autoresizingmask/center-portrait.png" /&gt;&lt;/p&gt;

&lt;p&gt;But as you could imagine as soon as we rotate the device the center view is gone&lt;/p&gt;

&lt;p&gt;&lt;img alt="center-landscape-bad" src="https://whackylabs.com/assets/autoresizingmask/center-landscape-bad.png" /&gt;&lt;/p&gt;

&lt;p&gt;The solution this time is to have all the margins be flexible.&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="n"&gt;centerVw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;autoresizingMask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flexibleTopMargin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flexibleBottomMargin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                             &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flexibleLeftMargin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flexibleRightMargin&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="center-landscape-good" src="https://whackylabs.com/assets/autoresizingmask/center-landscape-good.png" /&gt;&lt;/p&gt;

&lt;p&gt;The good thing is that this is still using the auto layout based system. So you could add another subview using the regular constraints based layout if you wish. For example, if we wanted to add another subview but aligned to the &lt;code class="language-plaintext highlighter-rouge"&gt;centerVw&lt;/code&gt;&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;centerOffsetVw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UIView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSubview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;centerOffsetVw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;centerOffsetVw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;backgroundColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;systemOrange&lt;/span&gt;
&lt;span class="n"&gt;centerOffsetVw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;translatesAutoresizingMaskIntoConstraints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="kt"&gt;NSLayoutConstraint&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;activate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="n"&gt;centerOffsetVw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;heightAnchor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;equalTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;centerVw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;heightAnchor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;centerOffsetVw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;widthAnchor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;equalTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;centerVw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;widthAnchor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;centerOffsetVw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;leadingAnchor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;equalTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;centerVw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;leadingAnchor&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;centerOffsetVw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;topAnchor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;equalTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;centerVw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;topAnchor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;constant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cornerSize&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="center-offset-portrait" src="https://whackylabs.com/assets/autoresizingmask/center-offset-portrait.png" /&gt;
&lt;img alt="center-offset-landscape" src="https://whackylabs.com/assets/autoresizingmask/center-offset-landscape.png" /&gt;&lt;/p&gt;

&lt;p&gt;But the bad thing is you can’t reliably add constraints to a view that is using &lt;code class="language-plaintext highlighter-rouge"&gt;autoresizingMask&lt;/code&gt;. If you try to you might get layout conflicts. You know the familiar giant wall of errors:&lt;/p&gt;

&lt;div class="language-plaintext highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;[LayoutConstraints] Unable to simultaneously satisfy constraints.
	Probably at least one of the constraints in the following list is one you don't want. 
	Try this: 
		(1) look at each constraint and try to figure out which you don't expect; 
		(2) find the code that added the unwanted constraint or constraints and fix it. 
	(Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "&amp;lt;NSAutoresizingMaskLayoutConstraint:0x600003e0de00 h=&amp;amp;-&amp;amp; v=&amp;amp;-&amp;amp; UIView:0x13db08780.minX == 0.500507*UIView:0x13d908cf0.width - 32.0324   (active)&amp;gt;",
    "&amp;lt;NSAutoresizingMaskLayoutConstraint:0x600003e0ddb0 h=--&amp;amp; v=--&amp;amp; UIView:0x13db08900.minX == 0   (active, names: '|':UIView:0x13d908cf0 )&amp;gt;",
    "&amp;lt;NSLayoutConstraint:0x600003e11630 UIView:0x13db08900.leading == UIView:0x13db08780.leading   (active)&amp;gt;",
    "&amp;lt;NSLayoutConstraint:0x600003e0d950 'UIView-Encapsulated-Layout-Width' UIView:0x13d908cf0.width == 393   (active)&amp;gt;"
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Since now you know so much about &lt;code class="language-plaintext highlighter-rouge"&gt;autoresizingMask&lt;/code&gt;, maybe it’s time you should also be able to read this error message. Specially the secret code written after &lt;code class="language-plaintext highlighter-rouge"&gt;NSAutoresizingMaskLayoutConstraint&lt;/code&gt;. The part that might read like &lt;code class="language-plaintext highlighter-rouge"&gt;h=--&amp;amp; v=--&amp;amp;&lt;/code&gt; is nothing other than the &lt;code class="language-plaintext highlighter-rouge"&gt;autoresizingMask&lt;/code&gt;. The &lt;code class="language-plaintext highlighter-rouge"&gt;h&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;v&lt;/code&gt; are obviously horizontal and vertical directions and the next 3 characters are &lt;code class="language-plaintext highlighter-rouge"&gt;[LeftMargin][Width][RightMargin]&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;[TopMargin][Height][BottomMargin]&lt;/code&gt; with &lt;code class="language-plaintext highlighter-rouge"&gt;-&lt;/code&gt; for fixed values and &lt;code class="language-plaintext highlighter-rouge"&gt;&amp;amp;&lt;/code&gt; for flexible values. And since we only set the flexible values with &lt;code class="language-plaintext highlighter-rouge"&gt;autoresizingMask&lt;/code&gt; so that mean we only care about the values set to &lt;code class="language-plaintext highlighter-rouge"&gt;&amp;amp;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So, for example &lt;code class="language-plaintext highlighter-rouge"&gt;h=--&amp;amp; v=--&amp;amp;&lt;/code&gt; means &lt;code class="language-plaintext highlighter-rouge"&gt;[.flexibleRightMargin, .flexibleBottomMargin]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So as a final task lets write a parser that could take input as &lt;code class="language-plaintext highlighter-rouge"&gt;autoresizingMask&lt;/code&gt; encoded as &lt;code class="language-plaintext highlighter-rouge"&gt;String&lt;/code&gt; and set the &lt;code class="language-plaintext highlighter-rouge"&gt;autoresizingMask&lt;/code&gt; just for fun.&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="kt"&gt;UIView&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// parse format like "h=--&amp;amp; v=--&amp;amp;"&lt;/span&gt;
  &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;setMask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;autoresizingMask&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AutoresizingMask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;subformats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;components&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;separatedBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;" "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;subformat&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;subformats&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;comps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subformat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;components&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;separatedBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"="&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;axis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;comps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;maskFormat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;comps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maskFormat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;axisMask&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;AutoresizingMask&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flexibleLeftMargin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flexibleWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flexibleRightMargin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flexibleTopMargin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flexibleHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flexibleBottomMargin&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;axis&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"v"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;maskFormat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enumerated&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"&amp;amp;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;autoresizingMask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;axisMask&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;autoresizingMask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;autoresizingMask&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And now we can also set the mask with &lt;code class="language-plaintext highlighter-rouge"&gt;bgVw.setMask(format: "h=-&amp;amp;- v=-&amp;amp;-")&lt;/code&gt;.&lt;/p&gt;</description><author>Whacky Labs</author><pubDate>Mon, 25 Sep 2023 22:19:00 GMT</pubDate><guid isPermaLink="true">https://whackylabs.com/swift/uikit/layout/2023/09/25/autoresizing-mask/</guid></item><item><title>Eastern Poland by bike - adventure on two wheels</title><link>https://kaszkowiak.org/en/blog/eastern-poland-bike-trip/</link><description>Beautiful views, wilderness camps and duct tape. Read about the cycling adventures of a thousand kilometers and eleven days of riding!</description><author>Blog on Maciej Kaszkowiak</author><pubDate>Mon, 25 Sep 2023 21:50:00 GMT</pubDate><guid isPermaLink="true">https://kaszkowiak.org/en/blog/eastern-poland-bike-trip/</guid></item><item><title>Using custom SSH keys with Git</title><link>https://rolisz.ro/2023/using-custom-ssh-keys-with-git/</link><description>&lt;p&gt;As a freelancer, I work for many clients, who have their source code in many different places, often self hosted. I generally create &lt;a href="https://rolisz.ro/2018/08/12/setting-up-ssh-keys/"&gt;new SSH keys&lt;/a&gt; for each separate platform. Unfortunately, Git doesn&amp;apos;t provide an option for what keys to use, you have to configure this in the&lt;/p&gt;</description><author>rolisz's blog</author><pubDate>Mon, 25 Sep 2023 17:59:02 GMT</pubDate><guid isPermaLink="true">https://rolisz.ro/2023/using-custom-ssh-keys-with-git/</guid></item><item><title>Export Things to Todoist</title><link>http://evantravers.com/articles/2023/09/25/export-things-to-todoist/</link><description>&lt;div class="admonition download"&gt;
&lt;p&gt;&lt;a href="https://www.icloud.com/shortcuts/92b5ab39f9a04548bcc393b1b9fb70e7"&gt;📥 Download Things ➡️ Todoist.shortcut&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonition update"&gt;
&lt;div class="admonition__title"&gt;Updates&lt;/div&gt;
&lt;p&gt;&lt;ul&gt;
&lt;li&gt;2024-12 I've updated the shortcut after Things' lovely API update.&lt;/li&gt;
&lt;li&gt;2026-06 Lucas emailed me to tell me Todoist's API URL has changed.&lt;/li&gt;
&lt;/ul&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Todoist recently rolled out &lt;a href="https://todoist.com/help/articles/5332270837276"&gt;a new version featuring task duration and therefore time-blocking&lt;/a&gt;. 🤯&lt;/p&gt;
&lt;p&gt;So I've been slaving over a crazy Shortcut to export my Things data to Todoist. 🤷 The pseudocode is basically:&lt;/p&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-plaintext" tabindex="0"&gt;&lt;div class="line"&gt;Get API key
&lt;/div&gt;&lt;div class="line"&gt;Make Lookup dictionary
&lt;/div&gt;&lt;div class="line"&gt;For each Area in Things:
&lt;/div&gt;&lt;div class="line"&gt;    Create Todoist Project
&lt;/div&gt;&lt;div class="line"&gt;    Store Things ID -&amp;gt; Todoist Project ID in Lookup
&lt;/div&gt;&lt;div class="line"&gt;    For each Project in Area:
&lt;/div&gt;&lt;div class="line"&gt;        Create Todoist Project with Area ID as Parent
&lt;/div&gt;&lt;div class="line"&gt;        Store Things ID -&amp;gt; Todoist Project ID in Lookup
&lt;/div&gt;&lt;div class="line"&gt;        For each Header in Project:
&lt;/div&gt;&lt;div class="line"&gt;            Create Todoist Section with Project as Parent
&lt;/div&gt;&lt;div class="line"&gt;            Store Header Name -&amp;gt; Section ID in Lookup
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;For each To-Do:
&lt;/div&gt;&lt;div class="line"&gt;    If has a Header:
&lt;/div&gt;&lt;div class="line"&gt;        Create Todoist Task using the Lookup to find Header's Section ID
&lt;/div&gt;&lt;div class="line"&gt;        Set Task ID to returned Todoist Task ID
&lt;/div&gt;&lt;div class="line"&gt;    Else
&lt;/div&gt;&lt;div class="line"&gt;        If has a Parent:
&lt;/div&gt;&lt;div class="line"&gt;            Find Parent ID -&amp;gt; Todoist Project ID using Lookup
&lt;/div&gt;&lt;div class="line"&gt;            Create Todoist Task with Parent ID
&lt;/div&gt;&lt;div class="line"&gt;            Set Task ID to returned Todoist Task ID
&lt;/div&gt;&lt;div class="line"&gt;        Else
&lt;/div&gt;&lt;div class="line"&gt;            Create Todoist Task
&lt;/div&gt;&lt;div class="line"&gt;            Set Task ID to returned Todoist Task ID
&lt;/div&gt;&lt;div class="line"&gt;    If Things To-Do has Deadline:
&lt;/div&gt;&lt;div class="line"&gt;        Update Task ID with Deadline
&lt;/div&gt;&lt;div class="line"&gt;    If Things To-Do has When:
&lt;/div&gt;&lt;div class="line"&gt;        Create &amp;quot;Start: Task&amp;quot; sub-task with Parent -&amp;gt; Task ID
&lt;/div&gt;&lt;div class="line"&gt;    If Things To-Do has Checklist:
&lt;/div&gt;&lt;div class="line"&gt;        For each item in Checklist:
&lt;/div&gt;&lt;div class="line"&gt;            Create Sub-Task with Parent -&amp;gt; Task ID
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As complex as that is... &lt;a href="https://images.evantravers.com/articles/2023/09/shortcut.JPG"&gt;it's worse in the Shortcuts editor.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Programming in Shortcuts sometimes feels like parenting a toddler: you have to say the same things multiple times, and most of the time neither you nor the child  can remember if you've already dealt with this issue. I &lt;em&gt;swear&lt;/em&gt; Shortcuts destroys or forgets a nested variable at random.&lt;/p&gt;
&lt;p&gt;And the scroll-jacking! If Shortcuts would stop yanking my screen around! But I digress:&lt;/p&gt;
&lt;p&gt;If nothing else… some things I learned:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Don't. Copy. Actions. With. Variables. 😤&lt;/li&gt;
&lt;li&gt;Use Reveal on nested variables to ensure it is pointed at the right objects.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The export currently:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;translates Areas, Projects, and Headers into nested Projects with Sections&lt;/li&gt;
&lt;li&gt;imports:
&lt;ul&gt;
&lt;li&gt;Title and Content&lt;/li&gt;
&lt;li&gt;&lt;strike&gt;Tags&lt;/strike&gt; (For some reason, the Shortcut Export &lt;em&gt;wipes&lt;/em&gt; this out every time!)&lt;/li&gt;
&lt;li&gt;When (as a meta-start task)&lt;/li&gt;
&lt;li&gt;Deadline&lt;/li&gt;
&lt;li&gt;Checklists&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It does not:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;save Project notes&lt;/li&gt;
&lt;li&gt;handle repetition schedules&lt;/li&gt;
&lt;li&gt;Reminders&lt;/li&gt;
&lt;li&gt;Evening&lt;/li&gt;
&lt;li&gt;import Logged projects or tasks&lt;/li&gt;
&lt;li&gt;handle Headers with the same string in different Projects. I just renamed mine.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It runs relatively quickly on my small-ish database of 200ish active tasks. It has to do a lot of little API calls to function, so it's possible you may run into Todoist's rate limit with a larger database.&lt;/p&gt;
&lt;h2&gt;&lt;a class="anchor" href="#install" id="install"&gt;&lt;/a&gt;Install&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Download the Shortcut here: &lt;a href="https://www.icloud.com/shortcuts/92b5ab39f9a04548bcc393b1b9fb70e7"&gt;Things ➡️ Todoist&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Be on the &lt;a href="https://todoist.com/pricing"&gt;Pro plan of Todoist&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://todoist.com/help/articles/find-your-api-token"&gt;Get your API key&lt;/a&gt; for Todoist ready.&lt;/li&gt;
&lt;li&gt;Run the shortcut, pasting in the API when prompted.&lt;/li&gt;
&lt;li&gt;Reply &amp;quot;Always&amp;quot; when Shortcuts asks permission to use Things data.&lt;/li&gt;
&lt;li&gt;Profit.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;&lt;a class="anchor" href="#" id=""&gt;&lt;/a&gt;…&lt;/h2&gt;
&lt;p&gt;Shortcuts are finicky… but if you run into any problems, &lt;a href="/contact"&gt;drop me a line&lt;/a&gt; and I'll try to help.&lt;/p&gt;</description><author>trv.rs</author><pubDate>Mon, 25 Sep 2023 11:30:00 GMT</pubDate><guid isPermaLink="true">http://evantravers.com/articles/2023/09/25/export-things-to-todoist/</guid></item><item><title>Skouf.com redesign - part 3</title><link>https://www.skouf.com/posts/skouf-dot-com-redesign-part-3/</link><description>In the final part of my website redesign, I finish things off and simplify by merging my blog with my website, inspired by striving to be simple and other personal sites I&amp;rsquo;ve come across in the last year or so</description><author>skouf.com</author><pubDate>Mon, 25 Sep 2023 11:25:42 GMT</pubDate><guid isPermaLink="true">https://www.skouf.com/posts/skouf-dot-com-redesign-part-3/</guid></item><item><title>Cultivating Your Developer Toolbox</title><link>https://medium.com/better-programming/cultivate-your-developer-toolbox-7f0c89505e55?source=rss-65d40a7904f------2</link><description>&lt;h3&gt;Cultivate Your Developer Toolbox&lt;/h3&gt;&lt;h4&gt;Stop writing throw-away scripts to solve your day-to-day engineering challenges&lt;/h4&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/0*FsCvblCFoqXXjKW-.png" /&gt;&lt;/figure&gt;&lt;p&gt;One of the highest return on investment practices I’ve adopted in my software engineering career is changing how I think about scripts, one-liners, prototypes, and scratchpads. Instead of one-off, throw-away scripts, I think of them as “tools” that I keep in my “toolbox”.&lt;/p&gt;&lt;p&gt;It isn’t a revolutionary idea, being adjacent to a “second brain,” professional journal, or physical toolbox, yet I’m surprised how few of my peers implement the simple practice and feel compelled to share. The practice is now high on my list of new hire tips and tricks, right under finding a mentor.&lt;/p&gt;&lt;p&gt;Copying a role model on my team almost a decade ago I started a ZachStuff git repo. It started as really just “stuff” — a junk drawer of random bash scripts. Over time ZachStuff has become a resource I reference weekly, and it differentiates me from my peers, in a minor way, with how fast I can achieve day-to-day engineering tasks.&lt;/p&gt;&lt;p&gt;Over time, each new tool is faster to write, higher quality, and more powerful. Not just because I learn from the experience of making tools — mastering design patterns, frameworks, libraries, etc. Rather, the value is also the ever-increasing corpus of known-good solutions to copy from.&lt;/p&gt;&lt;p&gt;Many day-to-day engineering challenges share a small number of problem kernels. So copying a working tool skips past the boilerplate to more quickly get down to business. There are only so many versions of “get the logs and search for Foo,” “build and run a little bit of code in the cloud somewhere,” or “make some chained AWS API calls,” that I tend to do several times a week.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/0*vBUTcg-dbLudTnyz.png" /&gt;&lt;/figure&gt;&lt;p&gt;Below is an example (which was also the motivation for this post!):&lt;/p&gt;&lt;p&gt;Eight years ago I joined a new engineering team and was working on a ramp-up task. My job was to hunt for clues for a bug by joining many Big production log streams of various formats to construct a normalized timeline of events for each instance of the bug.&lt;/p&gt;&lt;p&gt;There are tons of ways to dive through logs: from brute-forcing via the &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/WhatIsCloudWatchLogs.html"&gt;CloudWatch&lt;/a&gt; browser console which wouldn’t cut it due to volume, to deploying a logging service backed by expensive document search DBs seemed overkill and would go mostly unused.&lt;/p&gt;&lt;p&gt;I thought a reasonable balance was to launch a small &lt;a href="https://docs.aws.amazon.com/emr/latest/ReleaseGuide/emr-spark.html"&gt;EMR/Spark&lt;/a&gt; app to fetch, transform, normalize, join, and search logs then log results. I only intended to use this thing once. I spent a couple of days wrestling with EMR and Spark just enough to do the job. My small team didn’t own any Spark applications, never needed to do something like this before, and had no plans to do so in the future. The tool wasn’t obviously transferrable or worth production-realizing, so it was destined to be thrown away as soon as I got the search results I needed.&lt;/p&gt;&lt;p&gt;However, as I had just started keeping my “Stuff” repo, after I found the logs I was looking for I checked in the EMR/Spark log searching tool and wrote myself a README before moving on.&lt;/p&gt;&lt;p&gt;I had not just learned from the experience, which was my first ever use of Spark or Hadoop. But by checking in the application, with usage instructions, as ugly one-off junk as it was, I had created an artifact to use again. Maybe not verbatim, but at least to copy and modify to my needs.&lt;/p&gt;&lt;p&gt;Some years later, on an unrelated team with no experience with or need for big data tools, I needed to search through a huge AWS S3 bucket. I used my previous tool to “spin up a quick Spark cluster” to do the job pretty quickly. I copied the above-mentioned log diving tool I had built years before, updated libraries, added a few features and configuration knobs, and solved that day's problem quickly.&lt;/p&gt;&lt;p&gt;Because I had an existing Spark app to start from, not only did I finish the immediate job faster than if from scratch, but the new features an added that time around would make the next copy *even better.*&lt;/p&gt;&lt;p&gt;Now recently, years later yet again, I just found myself saving hours of time by re-using parts of that initial “junk drawer” Spark/EMR boilerplate yet again, which is why you’re reading this! (and hopefully finding it valuable). While now, after the third copy, it bears resemblance to the Ship of Theseus, I can see the progression over time since I’ve kept the copies around.&lt;/p&gt;&lt;h3&gt;Tips for Cultivating Your Toolbox&lt;/h3&gt;&lt;p&gt;I now have roughly 400 “tools” in my toolbox!&lt;/p&gt;&lt;p&gt;Pretty much everything I do that’s longer than a one-liner gets checked in. Most are regexes and piped commands to search files. Some tools however are huge multi-language CDK-deployed applications, like my own interpretation of &lt;a href="https://github.com/newsapps/beeswithmachineguns"&gt;Bees With Machine Guns&lt;/a&gt; for generating extreme load to a web app.&lt;/p&gt;&lt;p&gt;Over time, I’ve found the below practices have proven useful with my tools.&lt;/p&gt;&lt;h3&gt;Prefer Copies Over Abstraction&lt;/h3&gt;&lt;p&gt;Every time I come across a new problem similar to one I’ve already solved, I prefer copying the old tool over complicating an existing one. I then prune away what isn’t needed and expand the “shell” into the new problem.&lt;/p&gt;&lt;p&gt;Trying to build a multi-tool with enough config or abstraction to do many things well enough is difficult. In a production setting, it makes sense to get that kind of thing right, but for tools the goal is low time-to-completion. Copying is typically just faster (though not always!).&lt;/p&gt;&lt;p&gt;I think of it kind of like encountering an obscure nut and bolt during a home improvement project. You could buy an expensive infinitely adjustable wrench that can handle every shaped nut, or just have a drawer full of dozens of differently-sized cheap wrenches.&lt;/p&gt;&lt;p&gt;In the case of software tools, though, it’s as if you can pick up a wrench that’s close to what you need, magically duplicate it, then transmute it into the perfect shape for the job at hand. And also store infinitely many in your “drawer” at no cost.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/0*qqFOeAqp00xC6DP1.png" /&gt;&lt;/figure&gt;&lt;h3&gt;Keep Representative Data&lt;/h3&gt;&lt;p&gt;Another tip is to keep a representative sample of data alongside the data-heavy tools in your toolbox, making sure to follow best practices of data handling (anonymized, cleaned, no secrets, no PII, etc). Keeping that sample data nearby lets you rediscover how the tool works later or is a useful sandbox for copying to a new, very similar tool.&lt;/p&gt;&lt;p&gt;For example, a regex-y file manipulation script with complicated cat | grep | sed | awk should sit next to a 1000-line sample of the original file against which the script was authored. If that’s more than a few Kb, a pointer is fine too.&lt;/p&gt;&lt;p&gt;I think of it as keeping a few spare boards next to your saws in the garage. Your next sawing job is easier because, if needed, you can practice on the material you know the saw can handle.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/0*-8Hfmz1ci7L1z7OQ.png" /&gt;&lt;/figure&gt;&lt;h3&gt;Practice Using Standalone Columnar Data Tools&lt;/h3&gt;&lt;p&gt;Stand-alone (without a daemon) local columnar data tools are incredibly powerful when chained together. Some of my favorite tools are not much more than recs and &lt;a href="https://www.sqlite.org/index.html"&gt;SQLite&lt;/a&gt; glued together. &lt;a href="https://metacpan.org/pod/App::RecordStream"&gt;Recs&lt;/a&gt; is an underrated tool useful for converting STDOUT to “RECord Streams”. From there, into an SQLite db to perform queries, using joins, etc.&lt;/p&gt;&lt;p&gt;This type of tool is invaluable for reconstructing or finding a scenario in a distributed system from logs for debugging. By gluing together the standalone version of columnar tools, they’ll always work, don’t depend on the state of the system, and it’s natural to keep data and code co-located. That’s related to my next tip — packages.&lt;/p&gt;&lt;h3&gt;Tools are Packages&lt;/h3&gt;&lt;p&gt;My ZachStuff repo is a mono repo, full of many unrelated things. A recent practice I’ve adopted is keeping each tool as its own package in a subdirectory.&lt;/p&gt;&lt;p&gt;Tools in Node have their package.json, python has requirements.txt or setup.py, java tools have a pom.xml, docker has Dockerfile and docker-compose.yml, etc.&lt;/p&gt;&lt;p&gt;This avoids any dependency on system libraries, making each tool relatively portable. It also allows for the tool to become a “v0” of a shareable tool.&lt;/p&gt;&lt;p&gt;This also promotes you to think of tools as “buildable projects” that accept “arguments” to do what you want, which I find helpful to ensure the tool is solving the right problem.&lt;/p&gt;&lt;h3&gt;README&lt;/h3&gt;&lt;p&gt;Related to keeping tools as packages is also keeping a &lt;a href="https://www.makeareadme.com/"&gt;README&lt;/a&gt;. Write some amount of instructions for yourself to use later. Whether it's a sample usage and a one-line description or multi-step setup guide documentation, anything to help you get going on a copy of the tool quicker in the future is worthwhile.&lt;/p&gt;&lt;h3&gt;What To — And Not To — Keep in Your Toolbox&lt;/h3&gt;&lt;p&gt;Sometimes a one-off tool existing at all is an indicator that something is missing from your team’s software.&lt;/p&gt;&lt;p&gt;If you feel the need to script out chained commands to boot your service, build a package, or something that everyone on your team needs to do frequently, that is a smell that the script or process does not belong in your personal toolbox. Instead, that process belongs as close to the service or package as possible — like a build target or bin/ subdirectory in the package.&lt;/p&gt;&lt;p&gt;There’s some judgment involved, but some rules of thumb as to what belongs in your personal toolbox are:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;If it took you less than 5 minutes to get working, then it doesn’t need to be checked in anywhere. If it took you more than 1 hour, it should *definitely* be checked in *somewhere*.&lt;/li&gt;&lt;li&gt;If you think what you’re about to do would be immediately reusable by your teammates, and would take only a few hours to get into shippable shape, then it belongs in your team’s software packages.&lt;/li&gt;&lt;li&gt;Everything else should be checked into your personal toolbox repository&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Some more examples that do belong in your toolbox are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Prototypes for new product ideas&lt;/li&gt;&lt;li&gt;Hackathon projects&lt;/li&gt;&lt;li&gt;Scratch-pads used to learn a new library, language, or framework&lt;/li&gt;&lt;li&gt;That whacky grep | sed thing you ran during a Sev-1 to find broken host needle in the fleet haystack&lt;/li&gt;&lt;li&gt;A script you wrote for something random that doesn’t “belong” anywhere else&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Closing Thoughts&lt;/h3&gt;&lt;p&gt;I think of my “ZachStuff” git repo as my version of MacGyver’s pockets. The fuller MacGyver’s pockets were with string and rubber bands and paper clips and seemingly random junk, the more likely he was to be able to quickly rig something together to save the day.&lt;/p&gt;&lt;p&gt;A toolbox repo isn’t glamorous and doesn’t replace battle-tested operational tooling or any part of a functional production system. But it’s a practice I encourage you to keep, as I’ve found it to be very useful.&lt;/p&gt;&lt;p&gt;&lt;em&gt;This is a &lt;/em&gt;&lt;a href="https://zdwolfe.substack.com/p/cultivate-your-developer-toolbox"&gt;&lt;em&gt;cross-post&lt;/em&gt;&lt;/a&gt;&lt;em&gt; from my new Substack, zdwolfe.substack.com. If you enjoyed this, consider subscribing there for more of my posts.&lt;/em&gt;&lt;/p&gt;&lt;img alt="" height="1" src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=7f0c89505e55" width="1" /&gt;&lt;hr /&gt;&lt;p&gt;&lt;a href="https://medium.com/better-programming/cultivate-your-developer-toolbox-7f0c89505e55"&gt;Cultivating Your Developer Toolbox&lt;/a&gt; was originally published in &lt;a href="https://betterprogramming.pub"&gt;Better Programming&lt;/a&gt; on Medium, where people are continuing the conversation by highlighting and responding to this story.&lt;/p&gt;</description><author>Stories by Zach Wolfe on Medium</author><pubDate>Mon, 25 Sep 2023 09:35:56 GMT</pubDate><guid isPermaLink="true">https://medium.com/better-programming/cultivate-your-developer-toolbox-7f0c89505e55?source=rss-65d40a7904f------2</guid></item><item><title>Stoner (A Novel) by John Williams</title><link>https://apurva-shukla.me/bookshelf/stoner/</link><description>⭐ ⭐ ⭐ ⭐ ⭐ Stoner was a beautiful novel, belonging to a genre called an academic novel, since its stage is a university campus.

William…</description><author>Apurva Shukla's RSS Feed</author><pubDate>Mon, 25 Sep 2023 08:55:18 GMT</pubDate><guid isPermaLink="true">https://apurva-shukla.me/bookshelf/stoner/</guid></item><item><title>Prompting Improvements: 4x Accuracy in ‘The Reversal Curse’ Experiment 2</title><link>https://sidsite.com/posts/reversal-curse/</link><description>The Reversal Curse (Sep 2023, Berglund et al.) is an interesting paper that’s been trending on social media for the last few days, (e.g. Twitter thread by Neel Nanda here, Hacker News discussion here).</description><author>sidsite</author><pubDate>Mon, 25 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://sidsite.com/posts/reversal-curse/</guid></item><item><title>Deliberate Practice</title><link>https://backendhance.com/blog/2023/deliberate-practice/</link><description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Aktuell übe ich Schönschrift.&lt;/strong&gt; Meine Handschrift war bisher immer nur &amp;ldquo;praktisch&amp;rdquo;. Jeden Tag schreibe ich alle Buchstaben des Alphabets und konzentriere mich auf das Schriftbild.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Vor 5 Jahren habe ich Zehnfingerschreiben geübt.&lt;/strong&gt; Jeden Tag 10 Minuten auf &lt;a href="https://www.keybr.com/" rel="noopener noreferrer" target="_blank"&gt;https://www.keybr.com/&lt;i class="bx bx-link-external"&gt;&lt;/i&gt;&lt;/a&gt;
.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Vor 7 Jahren verbannte ich meine Maus in der IDE.&lt;/strong&gt; Ich musste lernen alles mit der Tastatur zu bedienen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Und vor 10 Jahren fokussierte ich mich auf die Shell.&lt;/strong&gt; Ich führte alle Operationen in meinem System dort aus.&lt;/p&gt;</description><author>Backendhance</author><pubDate>Mon, 25 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/deliberate-practice/</guid></item><item><title>Making it fast shouldn't be the last step</title><link>https://ntietz.com/blog/make-it-fast-from-the-start/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;There's a common quote in the software world that you should "make it work, make it right, then make it fast."&lt;sup class="footnote-reference" id="fr-1-1"&gt;&lt;a href="https://ntietz.com/blog/make-it-fast-from-the-start/#fn-1"&gt;[1]&lt;/a&gt;&lt;/sup&gt;
This is a catchy aphorism, and it is often taken as a rule.
But in its short form, it misses some crucial nuance.
Let's unpack it to see what's missing, then how to do things right.&lt;/p&gt;
&lt;h1 id="what-does-it-mean"&gt;What does it mean?&lt;/h1&gt;
&lt;p&gt;Unpacking the statement, we have three distinct phases.&lt;/p&gt;
&lt;p&gt;First, we &lt;em&gt;make it work&lt;/em&gt;.
In this step, you get something working.
It should handle a basic case of the problem you're solving, but doesn't need to handle edge cases.
Sometimes you might skip tests, sometimes you might make a mess.
But you show that it &lt;em&gt;can&lt;/em&gt; be done and figure out roughly what it will take.&lt;/p&gt;
&lt;p&gt;Then, we &lt;em&gt;make it right&lt;/em&gt;.
This step is where you tighten up all the loose ends from the first step.
Handle all the edge cases, test the code, clean up any messes, do any refactoring.
The end result here is a working artifact that meets all the requirements.&lt;/p&gt;
&lt;p&gt;And then, we &lt;em&gt;make it fast&lt;/em&gt;.
This is the step I see skipped all the time, and it's where you go back and speed things up.
You do some profiling, see how things perform, then tweak and improve until you have satisfactory performance.&lt;/p&gt;
&lt;h1 id="the-problem"&gt;The problem&lt;/h1&gt;
&lt;p&gt;Here's the problem:
You are in for a world of hurt if you leave "make it fast" for the last step.
And that's probably why we have so much slow software in the world.&lt;/p&gt;
&lt;p&gt;After you've gone through the trouble of making software right, if you have major performance problems, they are likely to be fundamental to the approach you took.
You'll be able to speed things up somewhat, but major improvements will require more invasive and painful refactoring.
This is often simply not given enough time, and we hack things up to limp by.&lt;/p&gt;
&lt;p&gt;The reality is that if you want to have a hope of making your software perform well, you have to think about performance from the beginning.
You wouldn't start making the software without thinking about what correctness is.
Nor should you start it without thinking about how to make it fast.&lt;/p&gt;
&lt;p&gt;When you start making something, to make it work, you have to have a conception of what "make it right" will look like so that you can design with that in mind and not back yourself into a corner.
It's exactly the same with "make it fast."&lt;/p&gt;
&lt;p&gt;You have to make sure from the outset that your architecture supports the performance you need.
Otherwise you may wind up with decisions that are difficult to reverse but stand between you and performance, and then you have to practically rewrite the whole thing.
And it's not just the architecture level.
Every line of code you write impacts performance.
People want to profile a codebase and find &lt;em&gt;the&lt;/em&gt; line of code that's making it slow, but usually it's endemic and spread throughout.
Small penalties spread throughout the whole project add up to a big total cost.&lt;/p&gt;
&lt;p&gt;Instead, this is a crucial part of "make it work."
In that first step where you handle the common cases and think about (but don't yet handle) the edge cases, you must do the same for performance.
Make sure that "make it work" includes an implicit "fast enough when in realistic conditions."
Then as you layer on correctness, you can keep ensuring performance is sufficient.&lt;/p&gt;
&lt;p&gt;But the aphorism &lt;em&gt;is&lt;/em&gt; rather catchy...&lt;/p&gt;
&lt;h1 id="we-need-a-different-saying"&gt;We need a different saying&lt;/h1&gt;
&lt;p&gt;Unfortunately, it's hard to capture nuance in an aphorism.
I think it's important to try, though:
This aphorism in particular is one I've heard used to justify some sloppy work which ended up painting projects into performance purgatory&lt;sup class="footnote-reference" id="fr-2-1"&gt;&lt;a href="https://ntietz.com/blog/make-it-fast-from-the-start/#fn-2"&gt;[2]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;So what do we say instead?&lt;/p&gt;
&lt;p&gt;I'm not sure.
Writing aphorisms isn't my forte!
The results from the LLMs I tried were also not great.
Suggestions are welcome, but I think the answer might be that we don't need an aphorism here.&lt;/p&gt;
&lt;p&gt;Instead, we lean into the fact that we're doing engineering and we have to design all the requirements into the software, including performance.
We don't need an aphorism to justify our work.
We just have to remember that performance does matter (and is &lt;em&gt;part&lt;/em&gt; of being correct for much software) and that it's a consideration throughout the entire process.&lt;/p&gt;
&lt;hr /&gt;
&lt;hr /&gt;&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-1"&gt;
&lt;p&gt;This is &lt;a href="https://wiki.c2.com/?MakeItWorkMakeItRightMakeItFast"&gt;often attributed&lt;/a&gt; to Kent Beck, but was published at least as early as 1983 by one Brian Kernighan. &lt;a href="https://ntietz.com/blog/make-it-fast-from-the-start/#fr-1-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-2"&gt;
&lt;p&gt;Pretty pleased with the alliteration. &lt;a href="https://ntietz.com/blog/make-it-fast-from-the-start/#fr-2-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 25 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/make-it-fast-from-the-start/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Easy Email Tokens in Pure Postgres</title><link>https://taylor.town/pg-email-token</link><description>select email_token(now(), 'hello@example.com');</description><author>taylor.town</author><pubDate>Mon, 25 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/pg-email-token</guid></item><item><title>Is Tech Losing Its Leverage?</title><link>https://olivergilan.com/is-tech-losing-its-leverage/</link><description>Software used to be the highest leverage differentiator between tech companies and the rest. I'm not so sure that's the case anymore.</description><author>Oliver Gilan</author><pubDate>Mon, 25 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://olivergilan.com/is-tech-losing-its-leverage/</guid></item><item><title>Vim tip 33: editing with text objects</title><link>https://learnbyexample.github.io/tips/vim-tip-33/</link><description>&lt;p&gt;Combining motions such as &lt;code&gt;w&lt;/code&gt;, &lt;code&gt;%&lt;/code&gt; and &lt;code&gt;f&lt;/code&gt; with editing commands like &lt;code&gt;d&lt;/code&gt;, &lt;code&gt;c&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; require precise positioning to be effective.&lt;/p&gt;
&lt;p&gt;Vim also provides a list of handy context based options to make certain editing use cases easier using the &lt;code&gt;i&lt;/code&gt; and &lt;code&gt;a&lt;/code&gt; text object selections. You can easily remember the difference between these two options by thinking &lt;code&gt;i&lt;/code&gt; as &lt;strong&gt;inner&lt;/strong&gt; and &lt;code&gt;a&lt;/code&gt; as &lt;strong&gt;around&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;kbd&gt;diw&lt;/kbd&gt; delete a word regardless of where the cursor is on that word
&lt;ul&gt;
&lt;li&gt;equivalent to using &lt;kbd&gt;de&lt;/kbd&gt; when the cursor is on the first character of the word&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;diW&lt;/kbd&gt; delete a WORD regardless of where the cursor is on that WORD&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;daw&lt;/kbd&gt; delete a word regardless of where the cursor is on that word as well as a space character to the left/right of the word depending on its position in the current sentence&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;dis&lt;/kbd&gt; delete a sentence regardless of where the cursor is on that sentence&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;yas&lt;/kbd&gt; copy a sentence regardless of where the cursor is on that sentence as well as a space character to the left/right&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;cip&lt;/kbd&gt; delete a paragraph regardless of where the cursor is on that paragraph and change to Insert mode&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;dit&lt;/kbd&gt; delete all characters within HTML/XML tags, nesting is taken care as well
&lt;ul&gt;
&lt;li&gt;see &lt;a href="https://vimhelp.org/motion.txt.html#tag-blocks"&gt;:h tag-blocks&lt;/a&gt; for details about corner cases&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;di&amp;quot;&lt;/kbd&gt; delete all characters within a pair of double quotes, regardless of where the cursor is within the quotes&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;da'&lt;/kbd&gt; delete all characters within a pair of single quotes along with the quote characters&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;ci(&lt;/kbd&gt; delete all characters within &lt;code&gt;()&lt;/code&gt; and change to Insert mode
&lt;ul&gt;
&lt;li&gt;works even if the parenthesis are spread over multiple lines, nesting is taken care as well&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;ya}&lt;/kbd&gt; copy all characters within &lt;code&gt;{}&lt;/code&gt; including the &lt;code&gt;{}&lt;/code&gt; characters
&lt;ul&gt;
&lt;li&gt;works even if the braces are spread over multiple lines, nesting is taken care as well&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; You can use a count prefix for nested cases. For example, &lt;kbd&gt;c2i{&lt;/kbd&gt; will clear the inner braces (including the braces, and this could be nested too) and then only the text between braces for the next level.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See &lt;a href="https://vimhelp.org/motion.txt.html#text-objects"&gt;:h text-objects&lt;/a&gt; for more details.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/vim_reference"&gt;Vim Reference Guide&lt;/a&gt; and &lt;a href="https://learnbyexample.github.io/curated_resources/vim.html"&gt;curated list of resources for Vim&lt;/a&gt;.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Mon, 25 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/vim-tip-33/</guid></item><item><title>San Francisco to Fort Bragg</title><link>https://ellie.wtf/life/san-francisco-to-fort-bragg/</link><description>&lt;p&gt;&lt;img alt="" src="https://img.ellie.wtf/i/4bcd4bd7f48f92b49ea0008f192aa8dd23921f2913e4694cb9a8a74e002841f3.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;I've been in California for a few days now, staying with a friend in San Francisco. Today we left for the first day of a 6-day motorcycle trip! I've decided I'm going to write more on my own website, whereas I used to post much more about my travels to instagram.&lt;/p&gt;
&lt;p&gt;Today we did 257 miles, and rode for just under 6 hours.&lt;/p&gt;
&lt;blockquote class="markdown-alert-warning"&gt;
&lt;p&gt;This post is pretty image heavy, and some of them are not optimised.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="" src="https://img.ellie.wtf/i/fc70886f296b79b55304055ec429e734a34bf99931cc4cc5115015b24bde18a9.jpeg" /&gt;&lt;/p&gt;
&lt;p&gt;We started at Spike's Coffee and Tea, in the Castro. From there, we rode over the Golden Gate Bridge and up Highway One. We could have stuck with hw 1 the entire route, but to keep things interesting (and take the twistier route), we had several detours!&lt;/p&gt;
&lt;p&gt;The first food stop was at Route One Bakery. It was pretty quiet (unsurprising for a Monday morning), but apparently at weekends is pretty busy with lots of bikers.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://img.ellie.wtf/i/51c4121d8b13cd29ff1df81dd6b8e02db2d3772534a801bd4f5dfd8dd869e184.jpeg" /&gt;&lt;/p&gt;
&lt;p&gt;I made friends with a chicken! She enjoyed the crumbs from my cookie&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://img.ellie.wtf/i/15b24b448f11e2dd473f3723abff3d721b1ca968cf61d7e01de6c15952b13197.jpeg" /&gt;&lt;/p&gt;
&lt;p&gt;After this stop, we rode for a while longer, on a mix of hw 1 and some twistier side roads. I really like how this part of the world is a big mix of mountains, forests, and coast&lt;/p&gt;
&lt;p&gt;The last time I had ridden this route was in &lt;a href="https://ellie.wtf/life/2022/"&gt;2022&lt;/a&gt;, and I stopped at a cafe (Cafe Aquatica) before turning back. Seeing as this time we were going to be going &lt;em&gt;much&lt;/em&gt; further north, I thought it would make a nice tea break.&lt;/p&gt;
&lt;p&gt;I'd been wanting to stop to take a photo, so after a period of riding uphill we found a good opportunity for a photo! It was so incredibly quiet that stopping in the middle of the road wasn't a problem.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://img.ellie.wtf/i/bcf4a32d0f838e553340200a167b691f90c7abdc388ec25f1748e5cfacd69a7b.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;This road was probably the "wildest" part yet, and went through a lot of forest! Eventually it rejoined hw 1, which we stayed on for a while before heading off onto Skaggs Springs Road. I'm naming this one specifically, as it was definitely my favourite part of the day! There was a really nice bridge that made another nice photo spot, and afterwards the corners were very smooth and sweeping.&lt;/p&gt;
&lt;p&gt;After crashing in the UK a few weeks ago, the past 2 days have been the first time I've been back on a bike. Having such a nice stretch of road really helped me feel more confident riding again&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://img.ellie.wtf/i/b3b627fb48719681938985af3c8189ff16af6032b62baebf93eaef21d1c2c421.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;After Skaggs, we weren't too far from our stop for the day! The majority of the day had been absolutely amazing, so of course we were due some minor issues.&lt;/p&gt;
&lt;p&gt;We had about 1hr 30 left when I felt a sting of pain on my neck. This isn't unusual, sometimes small rocks or other small debris get flicked up from the road. What was unusual was that the stinging didn't stop. We pulled over, and it turns out I'd been stung by a wasp! It had found its way inside my jacket, and luckily didn't keep stinging. In all the years I've been riding, this has never happened - but has been a bit of a fear.&lt;/p&gt;
&lt;p&gt;Rainy showers had been forecast pretty much all day, and so far we had dodged them. A few raindrops were starting to appear on my visor towards the end, but it didn't seem like it would be bad. It started to get really heavy, though we were riding through a redwood forest and were pretty well sheltered. The forests all smelled absolutely &lt;strong&gt;amazing&lt;/strong&gt;, which is a sense you totally miss out on while travelling in a car.&lt;/p&gt;
&lt;p&gt;The last hour or so was all rain, but I wasn't too bothered about it. Today I rode some of the best roads in a long time, and can't wait to see what tomorrow brings!&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://img.ellie.wtf/i/ac2a9b30383f66afa09cfd561c06a9e2554f1eb043568c719412640d1c986363.jpeg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://img.ellie.wtf/i/73f146707109be7b378e5a337a30404ff864ee7c3bca7913553ba11d754d07ea.jpeg" /&gt;&lt;/p&gt;
&lt;p&gt;Thank you to Michelle for her fantastic hospitality while I've been here, letting me borrow her bike, and for taking me on this trip&lt;/p&gt;</description><author>Ellie's Notes</author><pubDate>Mon, 25 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ellie.wtf/life/san-francisco-to-fort-bragg/</guid></item><item><title>Notes on paper</title><link>https://voussoir.net/writing/notes_on_paper</link><description>&lt;article&gt;
&lt;p&gt;&lt;a href="/writing"&gt;Back to writing&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a class="tag_link" href="/writing/tags/today_i_did_this"&gt;[today_i_did_this]&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Notes on paper&lt;/h1&gt;&lt;ol id="table_of_contents"&gt;Table of contents&lt;li&gt;&lt;a href="#introduction"&gt;Introduction&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="#habits"&gt;Habits&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="#scanning"&gt;Scanning&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="#some_pages"&gt;Some pages&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;a class="header_anchor_link" href="#introduction"&gt; (§)&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Over the past few weeks, I have been scanning my notes and homework from school using the nice professional scanners at my workplace. These notes have been sitting in my closet for several years because I don't want to keep them, but didn't want to throw them out until scanning them, and I've never had access to a scanner that could do all these pages.&lt;/p&gt;
&lt;p&gt;This has prompted me to reflect on some of the habits and choices that I follow in my handwritten notes. I realize there are entire online cultures of journaling and notetaking and notebook-buying, and I'm not here to compete with them. This is just what I do.&lt;/p&gt;
&lt;h2 id="habits"&gt;Habits&lt;a class="header_anchor_link" href="#habits"&gt; (§)&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Always put a date on every page, including pages that are given to you. For multiple pages in one day, it helps to add a page number.&lt;/p&gt;
&lt;p&gt;Always use loose leaf paper, never bound notebooks.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can hand a single sheet to someone.&lt;/li&gt;
&lt;li&gt;You can rewrite a sheet later and put it back in the same order, instead of keeping or tearing out the bad copy.&lt;/li&gt;
&lt;li&gt;Easier to cross reference a previous day without flipping back and forth.&lt;/li&gt;
&lt;li&gt;Easier to integrate with other material: pages you receive, homework you submit and get back.&lt;/li&gt;
&lt;li&gt;Easier to purchase the same or equivalent paper over the course of years, rather than developing an eclectic assortment of different notebooks or, worse, a brand dependency.&lt;/li&gt;
&lt;li&gt;Easier to scan.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Always use full-size paper (US Letter or A4 depending on your region) so that your notes stack well with printed material. A lot of lined/punched notebook paper is &lt;a href="/writing/notes_on_paper/2018-03-05.png"&gt;smaller&lt;/a&gt; than printer paper.&lt;/p&gt;
&lt;p&gt;Prefer to write in black ink rather than colored ink or pencil. Use quality pens that produce deep black lines, not cheap pens that produce off-black or hollow lines.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Easier to scan in 1-bit format.&lt;/li&gt;
&lt;li&gt;I like black, what can I say.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Always write on a single side of the paper, never the backside, unless forced to do so. The dollar cost of the paper is less than the &lt;a href="/writing/notes_on_paper/2018-03-06.png"&gt;inconvenience&lt;/a&gt; cost of double sided material.&lt;/p&gt;
&lt;p&gt;Resist the urge to cram everything onto one page. The telltale switch from big handwriting to "I'm running out of space" small handwriting at the bottom of the page is shameful. Two pages with healthy margins and spacing is better than one page of cramming.&lt;/p&gt;
&lt;p&gt;Consider using graph paper, even for non-mathematics, non-engineering notes. Prefer faint gridlines. You can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Accurately indent line continuations, sub-commentary, or &lt;a href="/writing/notes_on_paper/2017-10-24.png"&gt;nested bullet points&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Write in a &lt;a href="/writing/notes_on_paper/2017-02-21.png"&gt;two-column&lt;/a&gt; format.&lt;/li&gt;
&lt;li&gt;Cordon off side-thoughts in &lt;a href="/writing/notes_on_paper/2016-04-03.png"&gt;boxes&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Draw pictures or diagrams of &lt;a href="/writing/notes_on_paper/2017-02-02.png"&gt;consistent size and spacing&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Consider using plain, blank printer paper. At work, I am currently using blank printer paper for notetaking, and my "manuscript" is up to about 140 sheets. I don't think I would recommend this for structured school lectures, but it's good for unstructured meetings.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Very low cost per sheet.&lt;/li&gt;
&lt;li&gt;Complete freedom to draw (beware, too much freedom = &lt;a href="/writing/notes_on_paper/2017-10-17.png"&gt;wobbly, drifting text&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As a result of writing this article, I have decided to try going back to graph paper for my work notes. This time, instead of using store-bought graph paper, I'm just printing my own. I created an &lt;a href="/writing/notes_on_paper/graphpaper_025_grid_240.svg"&gt;svg file&lt;/a&gt; with a half-inch margin and a whisper-faint quarter-inch grid. This is working out great and I wish I had done it sooner.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pattern, margin, lightness, and paper stock tailored exactly to your preferences.&lt;/li&gt;
&lt;li&gt;Trying new patterns is completely free — no commitment to a reamful of one pattern.&lt;/li&gt;
&lt;li&gt;No dependence on brand or manufacturer. As long as you can find blank paper that suits you, you can make your graph paper.&lt;/li&gt;
&lt;li&gt;Cost per sheet still extremely low when using laser printers.&lt;/li&gt;
&lt;li&gt;Very consistent across pages — cheap store-bought graph paper often has inconsistent grid offset from page to page.&lt;/li&gt;
&lt;li&gt;Lightness of gridlines must be tuned per-printer by experimentation.&lt;/li&gt;
&lt;li&gt;The paper may become slightly curled or wavy due to the heat and rollers — the paper will need time and/or weight to flatten back out.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Treat yourself to heavier paper stock. The cheapest printer paper is usually &lt;a href="https://en.wikipedia.org/wiki/Grammage"&gt;20lb or 75gsm&lt;/a&gt;. Try something heavier like 28lb/105gsm or 32lb/120gsm. Heavier paper costs more, but it feels more pleasant to write on. If you enjoy it more, you'll do it more.&lt;/p&gt;
&lt;p&gt;Consider re-writing your notes after your class/meeting, so that you can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reorganize the information in a new order that you could not have foreseen when taking it down.&lt;/li&gt;
&lt;li&gt;Use slower, more careful handwriting.&lt;/li&gt;
&lt;li&gt;Replace abbreviations with full words and phrases.&lt;/li&gt;
&lt;li&gt;Eliminate the scratched-out mistakes and keep only the good parts.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have never used a digital tablet for notetaking, and I will not make the appeal to tradition that paper is always better. I can imagine it brings some advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Don't need to scan it.&lt;/li&gt;
&lt;li&gt;Instant OCR makes it searchable (assuming software support) (requires non-awful handwriting).&lt;/li&gt;
&lt;li&gt;Can mix typing for perfect legibility with drawing for visual aids.&lt;/li&gt;
&lt;li&gt;Easier to integrate photos taken on the spot.&lt;/li&gt;
&lt;li&gt;Infinite paper and ink.&lt;/li&gt;
&lt;li&gt;Undo button, layers, drag to rearrange.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But paper has its own advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cheap.&lt;/li&gt;
&lt;li&gt;Easier to lay out multiple sheets and review / cross-reference pages in any order — not limited to real estate of one screen and scrolling / tab-switching.&lt;/li&gt;
&lt;li&gt;You can hand someone a single sheet so they can read it while you work on other sheets — you're not handing them your entire system.&lt;/li&gt;
&lt;li&gt;Easier to integrate with material you receive.&lt;/li&gt;
&lt;li&gt;Open-note exams will probably only allow paper for quite some time yet.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="scanning"&gt;Scanning&lt;a class="header_anchor_link" href="#scanning"&gt; (§)&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I scanned everything at 600 PPI in 1-bit TIFF. The scanners at work don't seem to support lossless full color, and I'd rather have the aliasing and dithering artifacts of lossless 1-bit than the jpeggy, blocky artifacts of lossy color. This is an aesthetic preference and I think dithering looks cool anyway.&lt;/p&gt;
&lt;p&gt;I wrote &lt;a href="https://git.voussoir.net/voussoir/cmd/src/branch/master/imagesequence_to_images.py"&gt;imagesequence_to_images.py&lt;/a&gt; to turn the scanner's multi-page TIF file into separate PNGs.&lt;/p&gt;
&lt;p&gt;I use &lt;a href="https://git.voussoir.net/voussoir/cmd/src/branch/master/brename.py"&gt;brename.py&lt;/a&gt; to rename everything.&lt;/p&gt;
&lt;p&gt;When dealing with double-sided material and a single-sided scanner, I scanned the stack twice for the fronts and backs. Then, I did something like this:&lt;/p&gt;
&lt;div class="highlight batch"&gt;&lt;pre&gt;&lt;span class="k"&gt;md&lt;/span&gt; &lt;span class=""&gt;fronts&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;move&lt;/span&gt; &lt;span class=""&gt;fronts*.png&lt;/span&gt; &lt;span class=""&gt;fronts&lt;/span&gt;&lt;span class=""&gt;&lt;/span&gt;
&lt;span class="k"&gt;md&lt;/span&gt; &lt;span class=""&gt;backs&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;move&lt;/span&gt; &lt;span class=""&gt;backs*.png&lt;/span&gt; &lt;span class=""&gt;backs&lt;/span&gt;&lt;span class=""&gt;&lt;/span&gt;

&lt;span class="k"&gt;cd&lt;/span&gt; &lt;span class=""&gt;fronts&lt;/span&gt;&lt;span class=""&gt;&lt;/span&gt;

&lt;span class=""&gt;#&lt;/span&gt; &lt;span class=""&gt;Fronts&lt;/span&gt; &lt;span class=""&gt;will&lt;/span&gt; &lt;span class=""&gt;get&lt;/span&gt; &lt;span class=""&gt;even&lt;/span&gt; &lt;span class=""&gt;index&lt;/span&gt; &lt;span class=""&gt;numbers&lt;/span&gt;&lt;span class=""&gt;,&lt;/span&gt; &lt;span class=""&gt;starting&lt;/span&gt; &lt;span class=""&gt;from&lt;/span&gt; &lt;span class=""&gt;zero.&lt;/span&gt;&lt;span class=""&gt;&lt;/span&gt;
&lt;span class=""&gt;brename&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;f'{(index*2):03d}.png'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class=""&gt;&lt;/span&gt;

&lt;span class="k"&gt;cd&lt;/span&gt; &lt;span class=""&gt;..\backs&lt;/span&gt;&lt;span class=""&gt;&lt;/span&gt;

&lt;span class=""&gt;#&lt;/span&gt; &lt;span class=""&gt;Large&lt;/span&gt; &lt;span class=""&gt;number&lt;/span&gt; &lt;span class=""&gt;minus&lt;/span&gt; &lt;span class=""&gt;index&lt;/span&gt; &lt;span class=""&gt;to&lt;/span&gt; &lt;span class=""&gt;reverse&lt;/span&gt; &lt;span class=""&gt;the&lt;/span&gt; &lt;span class=""&gt;file&lt;/span&gt; &lt;span class=""&gt;order.&lt;/span&gt;&lt;span class=""&gt;&lt;/span&gt;
&lt;span class=""&gt;brename&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;f'{(999-index):03d}.png'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class=""&gt;&lt;/span&gt;

&lt;span class=""&gt;#&lt;/span&gt; &lt;span class=""&gt;Backs&lt;/span&gt; &lt;span class=""&gt;will&lt;/span&gt; &lt;span class=""&gt;get&lt;/span&gt; &lt;span class=""&gt;odd&lt;/span&gt; &lt;span class=""&gt;index&lt;/span&gt; &lt;span class=""&gt;numbers&lt;/span&gt;&lt;span class=""&gt;,&lt;/span&gt; &lt;span class=""&gt;starting&lt;/span&gt; &lt;span class=""&gt;from&lt;/span&gt; &lt;span class=""&gt;one.&lt;/span&gt;&lt;span class=""&gt;&lt;/span&gt;
&lt;span class=""&gt;brename&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;f'{(index*2)+1:03d}.png'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class=""&gt;&lt;/span&gt;

&lt;span class="k"&gt;cd&lt;/span&gt; &lt;span class=""&gt;..&lt;/span&gt;&lt;span class=""&gt;&lt;/span&gt;
&lt;span class="k"&gt;move&lt;/span&gt; &lt;span class=""&gt;fronts\*&lt;/span&gt; &lt;span class=""&gt;.&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;rmdir&lt;/span&gt; &lt;span class=""&gt;fronts&lt;/span&gt;&lt;span class=""&gt;&lt;/span&gt;
&lt;span class="k"&gt;move&lt;/span&gt; &lt;span class=""&gt;backs\*&lt;/span&gt; &lt;span class=""&gt;.&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;rmdir&lt;/span&gt; &lt;span class=""&gt;backs&lt;/span&gt;&lt;span class=""&gt;&lt;/span&gt;

&lt;span class=""&gt;#&lt;/span&gt; &lt;span class=""&gt;...&lt;/span&gt;&lt;span class=""&gt;&lt;/span&gt;
&lt;span class=""&gt;#&lt;/span&gt; &lt;span class=""&gt;Spend&lt;/span&gt; &lt;span class=""&gt;a&lt;/span&gt; &lt;span class=""&gt;few&lt;/span&gt; &lt;span class=""&gt;minutes&lt;/span&gt; &lt;span class=""&gt;manually&lt;/span&gt; &lt;span class=""&gt;deleting&lt;/span&gt; &lt;span class=""&gt;the&lt;/span&gt; &lt;span class=""&gt;blank&lt;/span&gt; &lt;span class=""&gt;backsides.&lt;/span&gt;&lt;span class=""&gt;&lt;/span&gt;
&lt;span class=""&gt;#&lt;/span&gt; &lt;span class=""&gt;...&lt;/span&gt;&lt;span class=""&gt;&lt;/span&gt;

&lt;span class=""&gt;#&lt;/span&gt; &lt;span class=""&gt;Reindex&lt;/span&gt; &lt;span class=""&gt;all&lt;/span&gt; &lt;span class=""&gt;the&lt;/span&gt; &lt;span class=""&gt;files&lt;/span&gt; &lt;span class=""&gt;so&lt;/span&gt; &lt;span class=""&gt;we&lt;/span&gt; &lt;span class=""&gt;can&lt;/span&gt; &lt;span class=""&gt;pretend&lt;/span&gt; &lt;span class=""&gt;the&lt;/span&gt; &lt;span class=""&gt;blank&lt;/span&gt; &lt;span class=""&gt;backsides&lt;/span&gt; &lt;span class=""&gt;didn't&lt;/span&gt; &lt;span class=""&gt;exist.&lt;/span&gt;&lt;span class=""&gt;&lt;/span&gt;
&lt;span class=""&gt;#&lt;/span&gt; &lt;span class=""&gt;Renaming&lt;/span&gt; &lt;span class=""&gt;will&lt;/span&gt; &lt;span class=""&gt;fail&lt;/span&gt; &lt;span class=""&gt;if&lt;/span&gt; &lt;span class=""&gt;the&lt;/span&gt; &lt;span class=""&gt;target&lt;/span&gt; &lt;span class=""&gt;filename&lt;/span&gt; &lt;span class=""&gt;already&lt;/span&gt; &lt;span class=""&gt;exists&lt;/span&gt;&lt;span class=""&gt;,&lt;/span&gt; &lt;span class=""&gt;so&lt;/span&gt; &lt;span class=""&gt;temporarily&lt;/span&gt;&lt;span class=""&gt;&lt;/span&gt;
&lt;span class=""&gt;#&lt;/span&gt; &lt;span class=""&gt;bump&lt;/span&gt; &lt;span class=""&gt;up&lt;/span&gt; &lt;span class=""&gt;to&lt;/span&gt; &lt;span class=""&gt;more&lt;/span&gt; &lt;span class=""&gt;digits&lt;/span&gt;&lt;span class=""&gt;,&lt;/span&gt; &lt;span class=""&gt;then&lt;/span&gt; &lt;span class=""&gt;back&lt;/span&gt; &lt;span class=""&gt;down.&lt;/span&gt;&lt;span class=""&gt;&lt;/span&gt;
&lt;span class=""&gt;brename&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;f'{index1:04d}.png'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class=""&gt;&lt;/span&gt;
&lt;span class=""&gt;brename&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;f'{index1:03d}.png'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class=""&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="some_pages"&gt;Some pages&lt;a class="header_anchor_link" href="#some_pages"&gt; (§)&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here are some pages that I thought were visually interesting. These are not perfect demonstrations of the habits above, and that's the point — over time I have realized things I should have been doing all along.&lt;/p&gt;
&lt;p&gt;I find there's something really engaging about being able to zoom in and pan around and pixel peep at these millimeter-sized features that, at the time of their making, only occupied a few tenths of a second of attention. The small writ large, as it were.&lt;/p&gt;
&lt;p&gt;I am including more pages here than most anybody will care to look through, but I couldn't pick any more to eliminate. I think they're kind of cool.&lt;/p&gt;
&lt;section&gt;
&lt;a href="/writing/notes_on_paper/2015-10-29.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2015-10-29.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2015-11-19.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2015-11-19.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2016-04-08.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2016-04-08.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2016-04-25.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2016-04-25.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2017-01-12.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2017-01-12.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2017-01-26.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2017-01-26.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2017-02-02.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2017-02-02.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2017-02-28.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2017-02-28.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2017-04-13.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2017-04-13.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2017-04-21.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2017-04-21.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2017-05-01.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2017-05-01.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2017-05-03.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2017-05-03.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2017-05-15.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2017-05-15.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2017-05-22.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2017-05-22.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2017-06-22.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2017-06-22.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2017-06-29.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2017-06-29.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2017-07-11.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2017-07-11.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2017-10-06.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2017-10-06.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2017-10-13.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2017-10-13.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2017-10-20.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2017-10-20.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2017-10-30.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2017-10-30.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2017-11-05.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2017-11-05.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2017-11-13.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2017-11-13.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2017-11-27.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2017-11-27.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2018-01-03.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2018-01-03.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2018-01-12.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2018-01-12.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2018-01-22.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2018-01-22.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2018-01-26.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2018-01-26.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2018-01-29.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2018-01-29.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2018-01-31.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2018-01-31.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2018-02-12.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2018-02-12.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2018-06-20.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2018-06-20.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2018-10-10.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2018-10-10.png" /&gt;&lt;/a&gt;
&lt;a href="/writing/notes_on_paper/2018-11-07.png"&gt;&lt;img height="512" src="/writing/notes_on_paper/small_2018-11-07.png" /&gt;&lt;/a&gt;
&lt;/section&gt;

&lt;hr /&gt;
&lt;p&gt;&lt;a href="https://git.voussoir.net/voussoir/voussoir.net/commits/master/voussoir.net/writing/notes_on_paper/notes_on_paper.md"&gt;View this document's history&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://git.voussoir.net/voussoir/voussoir.net/commit/d95c72e72997a8d8973926546c9944e3af2b276c"&gt;&lt;time datetime="2024-03-03T11:58:59-08:00"&gt;2024-03-03&lt;/time&gt; Add note about heavier paper stock.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://git.voussoir.net/voussoir/voussoir.net/commit/11ff76d768b69966eca9d759e955396e2334d12b"&gt;&lt;time datetime="2023-10-15T14:40:29-07:00"&gt;2023-10-15&lt;/time&gt; Add paragraph on printed graph paper.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://git.voussoir.net/voussoir/voussoir.net/commit/ce1d388279b781c375e4d645875145324a5f15ae"&gt;&lt;time datetime="2023-09-24T16:31:19-07:00"&gt;2023-09-24&lt;/time&gt; Add notes_on_paper.md.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;address&gt;Contact me: writing@voussoir.net&lt;/address&gt;
&lt;p&gt;If you would like to subscribe for more, add this to your RSS reader: &lt;a href="/writing/writing.atom" rel="alternate" type="application/atom+xml"&gt;https://voussoir.net/writing/writing.atom&lt;/a&gt;&lt;/p&gt;
&lt;/article&gt;</description><author>voussoir.net/writing</author><pubDate>Mon, 25 Sep 2023 02:31:19 GMT</pubDate><guid isPermaLink="true">https://voussoir.net/writing/notes_on_paper</guid></item><item><title>Ukulele chords table</title><link>http://localhost:4000/2023/09/25/ukulele-chords-table-extended.html</link><description>Ukulele chords table</description><author>ms75</author><pubDate>Mon, 25 Sep 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">http://localhost:4000/2023/09/25/ukulele-chords-table-extended.html</guid></item><item><title>Counterculture in the Age of Hyperconnectivity</title><link>https://venam.net/blog/philosophy/2023/09/25/counterculture.html</link><description>Life is pretty bland today! Indeed, we're the cozy spectators of homogenization through globalization, pushed by a series of ephemeral trends, one dying after another, and carried by consumer society. It's as if the cultural vibes all seem to walk in the same direction. What is this direction? Where did novelty go, have countercultures disappeared? Yet, they were omnipresent, bubbling and thriving, in yesteryears.  What is a counterculture anyway?</description><author>Venam's Blog — Patrick Louis (Lebanon)</author><pubDate>Mon, 25 Sep 2023 00:00:00 GMT</pubDate><guid isPermaLink="true">https://venam.net/blog/philosophy/2023/09/25/counterculture.html</guid></item><item><title>Old-School CGI Scripts!</title><link>https://blog.steve.fi/old_school_cgi_scripts_.html</link><description>&lt;p&gt;I'm not sure if I've talked about my job here, but I recently celebrated my one year anniversary - whilst on a company offsite trip to Sweden.  When I joined the company there were approximately 100 people employed by it.  Nowadays the numbers are much higher.&lt;/p&gt;

&lt;p&gt;Having more people around is pretty awesome, but I realized that there were a lot of people wandering around the office who I didn't recognize so it occurred to me to make a game of it.&lt;/p&gt;

&lt;p&gt;I had the idea I could write a slack bot to quiz me on my colleagues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Show a random face, using the Slack profile picture.&lt;/li&gt;
&lt;li&gt;Give a list of 5 names.&lt;/li&gt;
&lt;li&gt;Ask me which was correct.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I spent an hour messing around with various Slack APIs, and decided the whole thing was too much of a hassle.   Instead I wrote a simple script to download the details of all members of the workspace:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name.&lt;/li&gt;
&lt;li&gt;Email address.&lt;/li&gt;
&lt;li&gt;Profile picture URL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then using that data, &lt;code&gt;users.json&lt;/code&gt;, I hacked up a simple web application in Python, using the flask API.  There only needed to be two pages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A page ("/") to show five random images, each with five random names beneath them.&lt;/li&gt;
&lt;li&gt;A page ("/quiz") to receive the HTTP POST, and score.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All in all this took only two hours or so.  Old-school CGI is pretty awesome like that - Hidden values meant the whole thing could be stateless:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; &amp;lt;input type="hidden" name="1answer" value="Bob Smith" ..
 &amp;lt;input type="hidden" name="1profile" value="Sales" ..
 &amp;lt;input type="hidden" name="1url" value="https://.." ..

 &amp;lt;input type="hidden" name="2answer" value="Sally Smith" ..
 &amp;lt;input type="hidden" name="2profile" value="Sales" ..
 &amp;lt;input type="hidden" name="2url" value="https://.." ..
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The only downside is that I don't have any authentication, so there is no ability to have a leaderboard.  I've looked at the Okta samples and I think it would be easy to add, but I guess that would make it more complex and less portable.  That said I'm not sharing the code this time, so who cares if it is tied to the company?&lt;/p&gt;

&lt;p&gt;Anyway sometimes I forget how fast and easy it is to spinup a random virtual machine and present a HTTP(S) service for interactive use.  This is one of those times when I remembered.&lt;/p&gt;</description><author>Steve Kemp's Blog</author><pubDate>Sun, 24 Sep 2023 22:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.steve.fi/old_school_cgi_scripts_.html</guid></item><item><title>Are Area Layoffs in Tech Really That Scary?</title><link>https://mitrapunk.com/are-tech-layoffs-scary/</link><description>Layoffs, job changes, and career shifts might seem daunting, but remember, you bring a unique set of skills and experiences to the table.</description><author>Mitrapunk: Engineering Game</author><pubDate>Sun, 24 Sep 2023 17:38:13 GMT</pubDate><guid isPermaLink="true">https://mitrapunk.com/are-tech-layoffs-scary/</guid></item><item><title>Game Development Blog</title><link>https://mitrapunk.com/game-development-blog/</link><description>This is indie game development blog started in 2023. Mitra World is more than just a game; it's a revolution.</description><author>Mitrapunk: Engineering Game</author><pubDate>Sun, 24 Sep 2023 16:32:40 GMT</pubDate><guid isPermaLink="true">https://mitrapunk.com/game-development-blog/</guid></item><item><title>Progressively enhanced caching of JavaScript modules without bundling using import maps</title><link>https://qubyte.codes/blog/progressively-enhanced-caching-of-javascript-modules-without-bundling-using-import-maps</link><description>&lt;p&gt;I went to &lt;a href="https://2022.ffconf.org"&gt;ffconf 2022&lt;/a&gt; a couple of weeks ago, and two of the talks in
particular resonated with me... (more actually, but these felt actionable):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://youtu.be/vGYm9VdfJ8s"&gt;&amp;quot;This Talk is Under Construction: a love letter to the personal website&amp;quot;&lt;/a&gt; &lt;a href="https://localghost.dev/"&gt;Sophie Koonin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/CS-3bFo1XHA"&gt;&amp;quot;Working towards a greener world from behind the keyboard&amp;quot;&lt;/a&gt; Natalia Waniczek&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I really like having my own place on the web, and I've already put a fairly
substantial amount of effort into making it as gentle on the environment as
possible:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Semantic markup without lots of nesting reduces the size of HTML over the wire
(and probably makes it slightly less demanding to parse for the browser).&lt;/li&gt;
&lt;li&gt;Few images, served in the &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images"&gt;most efficient formats&lt;/a&gt; supported by the
browser making the request.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/Performance/Lazy_loading#images_and_iframes"&gt;Lazy loaded images&lt;/a&gt;. This is important for &lt;a href="/notes"&gt;my notes stream&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;CSS is minified, hashed, and has immutable cache headers. This means that your
browser will make a single network request for it and reuse it every time you
visit, unless your browser evicts it from the cache, or the CSS changes (which
is reflected by a change of the hash in the CSS file name).&lt;/li&gt;
&lt;li&gt;The site uses a static site generator, so requests for pages resolve to files
which are easily cached. There's no database serving queries behind the
scenes.&lt;/li&gt;
&lt;li&gt;As little JS as possible. Most pages have only enough JS to
&lt;a href="/blog/putting-back-the-service-worker"&gt;load a service worker&lt;/a&gt; as a progressive enhancement to allow offline access.
These pages work with JS disabled (you're reading one now).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I was inspired to put some time into reducing my footprint even further!&lt;/p&gt;
&lt;h3 id="caching-javascript"&gt;Caching JavaScript&lt;/h3&gt;
&lt;p&gt;The small elephant in the room are my JavaScript experiments (for example,
&lt;a href="/tags/GenerativeArt"&gt;generative art stuff&lt;/a&gt;). I like to build with small, reusable
modules, and until now I haven't bothered bundling. The most common solution is
to bundle the JS for each page, put a hash of the content in the file name, and
serves it with &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#immutable"&gt;immutable&lt;/a&gt;&lt;sup class="footnote-ref"&gt;&lt;a href="#footnote-1" id="footnote-ref-1"&gt;[1]&lt;/a&gt;&lt;/sup&gt; cache headers with a long &lt;code&gt;max-age&lt;/code&gt;, like the CSS
mentioned above.&lt;/p&gt;
&lt;p&gt;Why though? Resources which are immutably cached need no more network requests
once cached. This means fewer requests (especially on subsequent page views).
&lt;strong&gt;Less energy used&lt;/strong&gt; to transmit files. Less opportunity for network latency and
failure to delay or break features using JS. It's also kinder to mobile users
and their plans.&lt;/p&gt;
&lt;p&gt;For this little site the savings are going to be pretty modest, but it proves
that the approach works. Sites which use a lot more JS with frequent small
changes stand to benefit a lot more.&lt;/p&gt;
&lt;h3 id="bundling-versus-small-modules"&gt;Bundling versus small modules&lt;/h3&gt;
&lt;p&gt;I'd still rather not bundle though. The pool of small modules I've written for
my experiments are intended for reuse. The bundle for each page would
significantly overlap with the bundles of other pages, but would be cached
independently. A single character change to any module will invalidate all
bundles which include it.&lt;/p&gt;
&lt;p&gt;It would be far better to cache these modules independently. Consider this tree
of dependencies (imagine that &lt;code&gt;/a.js&lt;/code&gt; is the entry point):&lt;/p&gt;
&lt;svg class="flow-diagram" height="322" viewBox="0 0 207.9453125 322" xmlns="http://www.w3.org/2000/svg"&gt;
  &lt;title id="dependency-tree-no-hashes"&gt;Dependency tree with no hashes&lt;/title&gt;
  &lt;defs&gt;
    &lt;marker id="arrowhead" markerHeight="6" markerUnits="strokeWidth" markerWidth="8" orient="auto" refX="9" refY="5" viewBox="0 0 10 10"&gt;&lt;path class="arrowheadPath" d="M 0 0 L 10 5 L 0 10 z"&gt;&lt;/path&gt;&lt;/marker&gt;
    &lt;rect class="label-container" height="39" id="small-box" rx="0" ry="0" width="55.015625" x="-27.5078125" y="-19.5"&gt;&lt;/rect&gt;
    &lt;rect class="label-container" height="39" id="large-box" rx="0" ry="0" width="101.53125" x="-50.765625" y="-19.5"&gt;&lt;/rect&gt;
  &lt;/defs&gt;
  &lt;g transform="translate(0, 0)"&gt;
    &lt;g class="edgePaths"&gt;
      &lt;path class="arrow-path" d="M65.26878511235955,47L60.35159176029962,51.166666666666664C55.4343984082397,55.333333333333336,45.60001170411985,63.666666666666664,40.682818352059925,72C35.765625,80.33333333333333,35.765625,88.66666666666667,35.765625,92.83333333333333L35.765625,97" marker-end="url(#arrowhead)"&gt;&lt;/path&gt;
      &lt;path class="arrow-path" d="M111.29371488764045,47L116.21090823970037,51.166666666666664C121.1281015917603,55.333333333333336,130.96248829588015,63.666666666666664,135.8796816479401,72C140.796875,80.33333333333333,140.796875,88.66666666666667,140.796875,92.83333333333333L140.796875,97" marker-end="url(#arrowhead)"&gt;&lt;/path&gt;
      &lt;path class="arrow-path" d="M154.54889396067415,136L157.4873595505618,140.16666666666666C160.42582514044943,144.33333333333334,166.30275632022472,152.66666666666666,169.24122191011236,161C172.1796875,169.33333333333334,172.1796875,177.66666666666666,172.1796875,181.83333333333334L172.1796875,186" marker-end="url(#arrowhead)"&gt;&lt;/path&gt;
      &lt;path class="arrow-path" d="M127.04485603932585,136L124.10639044943821,140.16666666666666C121.16792485955057,144.33333333333334,115.29099367977528,152.66666666666666,112.35252808988764,164.25C109.4140625,175.83333333333334,109.4140625,190.66666666666666,109.4140625,205.5C109.4140625,220.33333333333334,109.4140625,235.16666666666666,112.35252808988764,246.75C115.29099367977528,258.3333333333333,121.16792485955057,266.6666666666667,124.10639044943821,270.8333333333333L127.04485603932585,275" marker-end="url(#arrowhead)"&gt;&lt;/path&gt;
      &lt;path class="arrow-path" d="M172.1796875,225L172.1796875,229.16666666666666C172.1796875,233.33333333333334,172.1796875,241.66666666666666,169.24122191011236,250C166.30275632022472,258.3333333333333,160.42582514044943,266.6666666666667,157.4873595505618,270.8333333333333L154.54889396067415,275" marker-end="url(#arrowhead)"&gt;&lt;/path&gt;
    &lt;/g&gt;
    &lt;g class="nodes"&gt;
      &lt;g class="node" transform="translate(88.28125,27.5)"&gt;
        &lt;use&gt;&lt;/use&gt;
        &lt;text&gt;/a.js&lt;/text&gt;
      &lt;/g&gt;
      &lt;g class="node" transform="translate(35.765625,116.5)"&gt;
        &lt;use&gt;&lt;/use&gt;
        &lt;text&gt;/b.js&lt;/text&gt;
      &lt;/g&gt;
      &lt;g class="node" transform="translate(140.796875,116.5)"&gt;
        &lt;use&gt;&lt;/use&gt;
        &lt;text&gt;/c.js&lt;/text&gt;
      &lt;/g&gt;
      &lt;g class="node" transform="translate(172.1796875,205.5)"&gt;
        &lt;use&gt;&lt;/use&gt;
        &lt;text&gt;/d.js&lt;/text&gt;
      &lt;/g&gt;
      &lt;g class="node" transform="translate(140.796875,294.5)"&gt;
        &lt;use&gt;&lt;/use&gt;
        &lt;text&gt;/e.js&lt;/text&gt;
      &lt;/g&gt;
    &lt;/g&gt;
  &lt;/g&gt;
&lt;/svg&gt;&lt;p&gt;This is more or less how all browsers saw JS on this site until recently. It
suffers from some problems... You can cache using ETags, but that's about as
far as you can get without transforming the JS in the modules. It means that
the browser only knows about each dependency as it encounters it in import
statements. We &lt;em&gt;could&lt;/em&gt; do some preloading, but whatever we do it's still a
request per module, even when the module has been downloaded before and
there are no changes.&lt;/p&gt;
&lt;p&gt;We could try to hash the content of each file, and put it in the filenames (the
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#cache_busting"&gt;cache-busting pattern&lt;/a&gt;):&lt;/p&gt;
&lt;svg class="flow-diagram" height="322" viewBox="0 0 247.9453125 322" xmlns="http://www.w3.org/2000/svg"&gt;
  &lt;title id="dependency-tree-with-hashes"&gt;Dependency tree with hashed file names&lt;/title&gt;
  &lt;g transform="translate(20, 0)"&gt;
    &lt;g class="edgePaths"&gt;
      &lt;path class="arrow-path" d="M65.26878511235955,47L60.35159176029962,51.166666666666664C55.4343984082397,55.333333333333336,45.60001170411985,63.666666666666664,40.682818352059925,72C35.765625,80.33333333333333,35.765625,88.66666666666667,35.765625,92.83333333333333L35.765625,97" marker-end="url(#arrowhead)"&gt;&lt;/path&gt;
      &lt;path class="arrow-path" d="M111.29371488764045,47L116.21090823970037,51.166666666666664C121.1281015917603,55.333333333333336,130.96248829588015,63.666666666666664,135.8796816479401,72C140.796875,80.33333333333333,140.796875,88.66666666666667,140.796875,92.83333333333333L140.796875,97" marker-end="url(#arrowhead)"&gt;&lt;/path&gt;
      &lt;path class="arrow-path" d="M154.54889396067415,136L157.4873595505618,140.16666666666666C160.42582514044943,144.33333333333334,166.30275632022472,152.66666666666666,169.24122191011236,161C172.1796875,169.33333333333334,172.1796875,177.66666666666666,172.1796875,181.83333333333334L172.1796875,186" marker-end="url(#arrowhead)"&gt;&lt;/path&gt;
      &lt;path class="arrow-path" d="M127.04485603932585,136L124.10639044943821,140.16666666666666C121.16792485955057,144.33333333333334,115.29099367977528,152.66666666666666,112.35252808988764,164.25C109.4140625,175.83333333333334,109.4140625,190.66666666666666,109.4140625,205.5C109.4140625,220.33333333333334,109.4140625,235.16666666666666,112.35252808988764,246.75C115.29099367977528,258.3333333333333,121.16792485955057,266.6666666666667,124.10639044943821,270.8333333333333L127.04485603932585,275" marker-end="url(#arrowhead)"&gt;&lt;/path&gt;
      &lt;path class="arrow-path" d="M172.1796875,225L172.1796875,229.16666666666666C172.1796875,233.33333333333334,172.1796875,241.66666666666666,169.24122191011236,250C166.30275632022472,258.3333333333333,160.42582514044943,266.6666666666667,157.4873595505618,270.8333333333333L154.54889396067415,275" marker-end="url(#arrowhead)"&gt;&lt;/path&gt;
    &lt;/g&gt;
    &lt;g class="nodes"&gt;
      &lt;g class="node" transform="translate(88.28125,27.5)"&gt;
        &lt;use&gt;&lt;/use&gt;
        &lt;text&gt;/a-63efa7.js&lt;/text&gt;
      &lt;/g&gt;
      &lt;g class="node" transform="translate(35.765625,116.5)"&gt;
        &lt;use&gt;&lt;/use&gt;
        &lt;text&gt;/b-913d04.js&lt;/text&gt;
      &lt;/g&gt;
      &lt;g class="node" transform="translate(140.796875,116.5)"&gt;
        &lt;use&gt;&lt;/use&gt;
        &lt;text&gt;/c-f61966.js&lt;/text&gt;
      &lt;/g&gt;
      &lt;g class="node" transform="translate(172.1796875,205.5)"&gt;
        &lt;use&gt;&lt;/use&gt;
        &lt;text&gt;/d-732056.js&lt;/text&gt;
      &lt;/g&gt;
      &lt;g class="node" transform="translate(140.796875,294.5)"&gt;
        &lt;use&gt;&lt;/use&gt;
        &lt;text&gt;/e-d2df5d.js&lt;/text&gt;
      &lt;/g&gt;
    &lt;/g&gt;
  &lt;/g&gt;
&lt;/svg&gt;&lt;p&gt;This lets us cache each file immutably. If there is a change to a file, then the
hash changes and the new file is downloaded.&lt;/p&gt;
&lt;p&gt;But there's a problem with this... when a file changes, its name will change.
When it's name changes, any files which depend on it will change since the
imports need to be updated! For example, if &lt;code&gt;/e.js&lt;/code&gt; changes, the imports in
&lt;code&gt;/c.js&lt;/code&gt; and &lt;code&gt;/d.js&lt;/code&gt; must be updated, which leads to a change in &lt;code&gt;/a.js&lt;/code&gt; too. The
changes go all the way from the updated module to the top!&lt;/p&gt;
&lt;svg class="flow-diagram" height="322" viewBox="0 0 247.9453125 322" xmlns="http://www.w3.org/2000/svg"&gt;
  &lt;title id="dependency-tree-with-invalidated-hashes"&gt;Dependency tree with invalidated hashed file names&lt;/title&gt;
  &lt;g transform="translate(20, 0)"&gt;
    &lt;g class="edgePaths"&gt;
      &lt;path class="arrow-path" d="M65.26878511235955,47L60.35159176029962,51.166666666666664C55.4343984082397,55.333333333333336,45.60001170411985,63.666666666666664,40.682818352059925,72C35.765625,80.33333333333333,35.765625,88.66666666666667,35.765625,92.83333333333333L35.765625,97" marker-end="url(#arrowhead)"&gt;&lt;/path&gt;
      &lt;path class="arrow-path" d="M111.29371488764045,47L116.21090823970037,51.166666666666664C121.1281015917603,55.333333333333336,130.96248829588015,63.666666666666664,135.8796816479401,72C140.796875,80.33333333333333,140.796875,88.66666666666667,140.796875,92.83333333333333L140.796875,97" marker-end="url(#arrowhead)"&gt;&lt;/path&gt;
      &lt;path class="arrow-path" d="M154.54889396067415,136L157.4873595505618,140.16666666666666C160.42582514044943,144.33333333333334,166.30275632022472,152.66666666666666,169.24122191011236,161C172.1796875,169.33333333333334,172.1796875,177.66666666666666,172.1796875,181.83333333333334L172.1796875,186" marker-end="url(#arrowhead)"&gt;&lt;/path&gt;
      &lt;path class="arrow-path" d="M127.04485603932585,136L124.10639044943821,140.16666666666666C121.16792485955057,144.33333333333334,115.29099367977528,152.66666666666666,112.35252808988764,164.25C109.4140625,175.83333333333334,109.4140625,190.66666666666666,109.4140625,205.5C109.4140625,220.33333333333334,109.4140625,235.16666666666666,112.35252808988764,246.75C115.29099367977528,258.3333333333333,121.16792485955057,266.6666666666667,124.10639044943821,270.8333333333333L127.04485603932585,275" marker-end="url(#arrowhead)"&gt;&lt;/path&gt;
      &lt;path class="arrow-path" d="M172.1796875,225L172.1796875,229.16666666666666C172.1796875,233.33333333333334,172.1796875,241.66666666666666,169.24122191011236,250C166.30275632022472,258.3333333333333,160.42582514044943,266.6666666666667,157.4873595505618,270.8333333333333L154.54889396067415,275" marker-end="url(#arrowhead)"&gt;&lt;/path&gt;
    &lt;/g&gt;
    &lt;g class="nodes"&gt;
      &lt;g class="node" transform="translate(88.28125,27.5)"&gt;
        &lt;use&gt;&lt;/use&gt;
        &lt;text class="error"&gt;/a-5b5be8.js&lt;/text&gt;
      &lt;/g&gt;
      &lt;g class="node" transform="translate(35.765625,116.5)"&gt;
        &lt;use&gt;&lt;/use&gt;
        &lt;text&gt;/b-913d04.js&lt;/text&gt;
      &lt;/g&gt;
      &lt;g class="node" transform="translate(140.796875,116.5)"&gt;
        &lt;use&gt;&lt;/use&gt;
        &lt;text class="error"&gt;/c-7534a8.js&lt;/text&gt;
      &lt;/g&gt;
      &lt;g class="node" transform="translate(172.1796875,205.5)"&gt;
        &lt;use&gt;&lt;/use&gt;
        &lt;text class="error"&gt;/d-25255e.js&lt;/text&gt;
      &lt;/g&gt;
      &lt;g class="node" transform="translate(140.796875,294.5)"&gt;
        &lt;use&gt;&lt;/use&gt;
        &lt;text class="error"&gt;/e-bb438d.js&lt;/text&gt;
      &lt;/g&gt;
    &lt;/g&gt;
  &lt;/g&gt;
&lt;/svg&gt;&lt;p&gt;This is a problem. I don't want to invalidate caching for modules which have no
meaningful code changes.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/WICG/import-maps"&gt;Import maps&lt;/a&gt; provide a way out of this quandary. They let us tell
the browser how to resolve imports. This means that instead of rewriting
the imports of each module, we keep them as they originally were. The engine
looks in the import map and resolves accordingly. Changing the content of a
module will mean only its entry in the import map gets updated.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-json"&gt;&lt;span class="hljs-punctuation"&gt;{&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;&amp;quot;imports&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;:&lt;/span&gt; &lt;span class="hljs-punctuation"&gt;{&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;&amp;quot;/a.js&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;:&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;/a-5b5be8.js&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;,&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;&amp;quot;/b.js&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;:&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;/b-913d04.js&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;,&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;&amp;quot;/c.js&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;:&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;/c-7534a8.js&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;,&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;&amp;quot;/d.js&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;:&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;/d-25255e.js&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;,&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;&amp;quot;/e.js&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;:&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;/e-bb438d.js&amp;quot;&lt;/span&gt;
  &lt;span class="hljs-punctuation"&gt;}&lt;/span&gt;
&lt;span class="hljs-punctuation"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For caching this is really nice. Your whole JS application can be cached, and
whenever a module is updated the browser only needs to make a request for the
updated content for that one module.&lt;/p&gt;
&lt;h3 id="implementation"&gt;Implementation&lt;/h3&gt;
&lt;p&gt;For a static site generator the setup is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Copy the files with their original names into the target directory.&lt;/li&gt;
&lt;li&gt;Copy each file a second time to the same target, but include a prefix and the
hash in the file name.&lt;/li&gt;
&lt;li&gt;Add an import map to each page which uses JS.&lt;/li&gt;
&lt;li&gt;Update the script entry point to use the hashed file.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The first point allows browsers which don't understand import maps yet to work
as they did before. Temporary redirects would work too, but mean more requests.
The prefixes of the files with content hashed in their names allows immutable
cache headers to be added when they're served (the second point).&lt;/p&gt;
&lt;p&gt;The last point was unexpected, but to spec (it's not just a Chrome quirk).
Browsers which don't understand import maps actually benefit from this change,
because they can at least cache the entry point.&lt;/p&gt;
&lt;p&gt;The third point hides &lt;em&gt;a lot&lt;/em&gt; of pain. As I add more experiments, I don't want
the import maps of other pages to grow, so I don't want just one import map for
all pages, but rather an import map for each page.&lt;/p&gt;
&lt;p&gt;When the files are copied and hashed, a big import map is generated. The code
I've written also parses (but does not modify) each module to determine its
dependencies.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-json"&gt;&lt;span class="hljs-punctuation"&gt;{&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;&amp;quot;/a.js&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;:&lt;/span&gt; &lt;span class="hljs-punctuation"&gt;{&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;&amp;quot;hashedFileName&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;:&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;/hashed-a-5b5be8.js&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;,&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;&amp;quot;dependencies&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;:&lt;/span&gt; &lt;span class="hljs-punctuation"&gt;[&lt;/span&gt;&lt;span class="hljs-string"&gt;&amp;quot;/b.js&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;,&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;/c.js&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;]&lt;/span&gt;
  &lt;span class="hljs-punctuation"&gt;}&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;,&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;&amp;quot;/b.js&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;:&lt;/span&gt; &lt;span class="hljs-punctuation"&gt;{&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;&amp;quot;hashedFileName&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;:&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;/hashed-b-913d04.js&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;,&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;&amp;quot;dependencies&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;:&lt;/span&gt; &lt;span class="hljs-punctuation"&gt;[&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;]&lt;/span&gt;
  &lt;span class="hljs-punctuation"&gt;}&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;,&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;&amp;quot;/c.js&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;:&lt;/span&gt; &lt;span class="hljs-punctuation"&gt;{&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;&amp;quot;hashedFileName&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;:&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;/hashed-c-7534a8.js&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;,&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;&amp;quot;dependencies&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;:&lt;/span&gt; &lt;span class="hljs-punctuation"&gt;[&lt;/span&gt;&lt;span class="hljs-string"&gt;&amp;quot;/d.js&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;,&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;e.js&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;]&lt;/span&gt;
  &lt;span class="hljs-punctuation"&gt;}&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;,&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;&amp;quot;/d.js&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;:&lt;/span&gt; &lt;span class="hljs-punctuation"&gt;{&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;&amp;quot;hashedFileName&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;:&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;/hashed-d-25255e.js&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;,&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;&amp;quot;dependencies&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;:&lt;/span&gt; &lt;span class="hljs-punctuation"&gt;[&lt;/span&gt;&lt;span class="hljs-string"&gt;&amp;quot;e.js&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;]&lt;/span&gt;
  &lt;span class="hljs-punctuation"&gt;}&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;,&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;&amp;quot;/e.js&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;:&lt;/span&gt; &lt;span class="hljs-punctuation"&gt;{&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;&amp;quot;hashedFileName&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;:&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;/hashed-e-bb438d.js&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;,&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;&amp;quot;dependencies&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;:&lt;/span&gt; &lt;span class="hljs-punctuation"&gt;[&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;]&lt;/span&gt;
  &lt;span class="hljs-punctuation"&gt;}&lt;/span&gt;
&lt;span class="hljs-punctuation"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When the map of each page is generated, the entry point is looked up in the big
import map, and its dependencies recursively added too. The hashed entry point
is used in the entry script tag.&lt;/p&gt;
&lt;h3 id="content-security-policy"&gt;Content Security Policy&lt;/h3&gt;
&lt;p&gt;While the import map specification allows for them to be external to the HTML of
a page, no browsers currently implement this&lt;sup class="footnote-ref"&gt;&lt;a href="#footnote-2" id="footnote-ref-2"&gt;[2]&lt;/a&gt;&lt;/sup&gt;. The HTML pages of my static
site have quite strict &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP"&gt;Content Security Policy (CSP)&lt;/a&gt; headers. These
headers prevent the execution of inline scripts and inline CSS, and I prefer not
to relax them.&lt;/p&gt;
&lt;p&gt;The way to resolve the conflict is to &lt;a href="https://content-security-policy.com/hash/"&gt;add the hash of the import map&lt;/a&gt;
to the CSP header of a page. This is like the server telling the browser &lt;em&gt;you
can't run scripts inlined in the page, except for those with this hash&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I once used Netlify edge functions to accomplish this&lt;sup class="footnote-ref"&gt;&lt;a href="#footnote-3" id="footnote-ref-3"&gt;[3]&lt;/a&gt;&lt;/sup&gt;, but now I
template a &lt;a href="https://docs.netlify.com/routing/headers/"&gt;headers file&lt;/a&gt; with an entry for each HTML page which needs a custom
CSP header for an import map hash.&lt;/p&gt;
&lt;h3 id="preloading"&gt;Preloading&lt;/h3&gt;
&lt;p&gt;One problem with lots of small modules I've not addressed yet is that on first
load, the browser only discovers what it needs to fetch as it reads the imports
of each module.&lt;/p&gt;
&lt;p&gt;Since the import maps are created specifically for each page, their values are
a list of URLs for the hashed files which &lt;em&gt;will&lt;/em&gt; be needed. It's possible to
preload the modules by adding &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/modulepreload"&gt;module preload&lt;/a&gt; links in the header. Like the
entry point, the browser won't use the import map to resolve JS modules. For
stuff in HTML you have to do that.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;link&lt;/span&gt; &lt;span class="hljs-attr"&gt;rel&lt;/span&gt;=&lt;span class="hljs-string"&gt;&amp;quot;modulepreload&amp;quot;&lt;/span&gt; &lt;span class="hljs-attr"&gt;href&lt;/span&gt;=&lt;span class="hljs-string"&gt;&amp;quot;/hashed-every-90ec60.js&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;link&lt;/span&gt; &lt;span class="hljs-attr"&gt;rel&lt;/span&gt;=&lt;span class="hljs-string"&gt;&amp;quot;modulepreload&amp;quot;&lt;/span&gt; &lt;span class="hljs-attr"&gt;href&lt;/span&gt;=&lt;span class="hljs-string"&gt;&amp;quot;/hashed-javascript-dc77dc.js&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;link&lt;/span&gt; &lt;span class="hljs-attr"&gt;rel&lt;/span&gt;=&lt;span class="hljs-string"&gt;&amp;quot;modulepreload&amp;quot;&lt;/span&gt; &lt;span class="hljs-attr"&gt;href&lt;/span&gt;=&lt;span class="hljs-string"&gt;&amp;quot;/hashed-module-8d541a.js&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;link&lt;/span&gt; &lt;span class="hljs-attr"&gt;rel&lt;/span&gt;=&lt;span class="hljs-string"&gt;&amp;quot;modulepreload&amp;quot;&lt;/span&gt; &lt;span class="hljs-attr"&gt;href&lt;/span&gt;=&lt;span class="hljs-string"&gt;&amp;quot;/hashed-needed-54bb3e.js&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;link&lt;/span&gt; &lt;span class="hljs-attr"&gt;rel&lt;/span&gt;=&lt;span class="hljs-string"&gt;&amp;quot;modulepreload&amp;quot;&lt;/span&gt; &lt;span class="hljs-attr"&gt;href&lt;/span&gt;=&lt;span class="hljs-string"&gt;&amp;quot;/hashed-later-05fbf2.js&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The solution isn't perfect though. A hypothetical problem (there are no browsers
like this at the time of writing) is when module preload is supported, but
import maps are not. This would lead to modules being fetched twice (hashed and
original file names). One way out of that would be to have the original
filenames respond with a temporary redirect to the file names with the hashes
in.&lt;/p&gt;
&lt;p&gt;One potential problem is the head of a document growing very large due to lots
of modules mapping to lots of links. In my case each experiment uses a handful
of modules, so the increase in the size of the head is not problematic. The
module preloads spec allows (but does not require) the browser to resolve the
child imports of preloaded modules, so a middle ground may be to preload just
some modules.&lt;/p&gt;
&lt;p&gt;One small saving I make is to exclude preload links for scripts which will
appear in script tags. This includes the &lt;code&gt;index.js&lt;/code&gt; file which applies to all
pages (mostly there to install a service worker), the entry point(s) of any
experiments, and the ruby state persistence script on any page with ruby
annotations. This means that most pages don't have an import map or any preload
links.&lt;/p&gt;
&lt;h3 id="interactions-with-the-service-worker"&gt;Interactions with the service worker&lt;/h3&gt;
&lt;p&gt;When a file is served with the immutable cache-control header, it'll either be
in the browser cache or it won't be (and will trigger a request to populate the
cache). The service worker isn't needed for such files. It's only there to make
decisions about more weakly cached files when browsing offline.&lt;/p&gt;
&lt;p&gt;The service worker intercepts all requests, but we don't want it to handle
ones cached immutably. By returning early from a fetch event handler the handler
delegates the request back to the browser and the browser cache will be used as
if the service worker isn't there at all.&lt;/p&gt;
&lt;p&gt;Since I'm prefixing immutable resources, with &lt;code&gt;hashed-&lt;/code&gt;, I can check the URL
of the request in the fetch event, and use the prefix to know not to continue.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;&lt;span class="hljs-title function_"&gt;addEventListener&lt;/span&gt;(&lt;span class="hljs-string"&gt;&amp;#x27;fetch&amp;#x27;&lt;/span&gt;, &lt;span class="hljs-function"&gt;&lt;span class="hljs-params"&gt;fetchEvent&lt;/span&gt; =&amp;gt;&lt;/span&gt; {
  &lt;span class="hljs-string"&gt;&amp;#x27;use strict&amp;#x27;&lt;/span&gt;;

  &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; isHashed = fetchEvent.&lt;span class="hljs-property"&gt;request&lt;/span&gt;.&lt;span class="hljs-property"&gt;url&lt;/span&gt;
    .&lt;span class="hljs-title function_"&gt;split&lt;/span&gt;(&lt;span class="hljs-string"&gt;&amp;#x27;/&amp;#x27;&lt;/span&gt;)
    .&lt;span class="hljs-title function_"&gt;pop&lt;/span&gt;()
    .&lt;span class="hljs-title function_"&gt;startsWith&lt;/span&gt;(&lt;span class="hljs-string"&gt;&amp;#x27;hashed-&amp;#x27;&lt;/span&gt;);

  &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (isHashed) {
    &lt;span class="hljs-keyword"&gt;return&lt;/span&gt;; &lt;span class="hljs-comment"&gt;// Delegate to the browser.&lt;/span&gt;
  }

  &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I like this. My site only uses a service worker to handle caching, and I'd
rather not have one at all. The less it does the better! In an ideal world, all
resources would be immutable, and only the HTML document would not be. In such
a world a request for a page resulting in a 304 response (not changed) would
also mean all resources are already cached, and no further requests are needed.&lt;/p&gt;
&lt;h3 id="conclusion-and-future-work"&gt;Conclusion and future work&lt;/h3&gt;
&lt;p&gt;When I first published this article, the only browsers to support import maps
were those based on Chrome. Firefox shipped support about a month after, and
finally Safari in March 2023. The big browsers now all support import maps!&lt;/p&gt;
&lt;p&gt;This work so far handles all JavaScript files I deploy except for the service
worker. The service worker may be tricky to handle (the URL of the service
worker script is important for the running worker and its cache). I'll update
this post as I improve coverage.&lt;/p&gt;
&lt;p&gt;For smaller applications which differ in composition from page to page (while
sharing many source modules) I think this solution has advantages over bundling.
There's less to cache (eliminates bundle overlap) and less to fetch when there
are changes (only fetch changed modules, not a whole bundle).&lt;/p&gt;</description><author>Qubyte Codes</author><pubDate>Sun, 24 Sep 2023 14:50:00 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/blog/progressively-enhanced-caching-of-javascript-modules-without-bundling-using-import-maps</guid></item><item><title>London to the Isle of Mull in a 4 year old electric car</title><link>https://michael-lewis.com/posts/london-to-the-isle-of-mull-in-a-4-year-old-electric-car/</link><description>&lt;h2 id="introduction"&gt;
  Introduction
  &lt;a class="heading-link" href="#introduction"&gt;
    &lt;i class="fa-solid fa-link" title="Link to heading"&gt;&lt;/i&gt;
    &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;
  &lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;This is a kind-of followup to &lt;a href="https://michael-lewis.com/posts/london-to-orkney-and-most-of-the-nc500-in-an-electric-car/"&gt;London to Orkney, and most of the NC500, in an electric car&lt;/a&gt; from August 2020.&lt;/p&gt;
&lt;p&gt;I had thought that the main themes would be how much the battery had degraded in 3-4 years, and how much busier the charging points were, but actually neither proved to be especially problematic.&lt;/p&gt;
&lt;h2 id="summary"&gt;
  Summary
  &lt;a class="heading-link" href="#summary"&gt;
    &lt;i class="fa-solid fa-link" title="Link to heading"&gt;&lt;/i&gt;
    &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;
  &lt;/a&gt;
&lt;/h2&gt;
&lt;h3 id="projected-range"&gt;
  Projected range
  &lt;a class="heading-link" href="#projected-range"&gt;
    &lt;i class="fa-solid fa-link" title="Link to heading"&gt;&lt;/i&gt;
    &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;
  &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;A common concern I&amp;rsquo;ve heard from non-EV-owners is battery degradation, i.e. how the maximum range per charge decreases over time. Some people seem afraid car batteries will be like their mobile phone batteries, where within a few years it&amp;rsquo;ll have degraded to the point of being useless.&lt;/p&gt;</description><author>Michael Ian Lewis</author><pubDate>Sun, 24 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://michael-lewis.com/posts/london-to-the-isle-of-mull-in-a-4-year-old-electric-car/</guid></item><item><title>How I would've designed Overwatch 2</title><link>http://pxtl.ca/2023/09/24/overwatch2-4v4/</link><description>&lt;p&gt;Okay I’ve been playing the Overwatch 2 anniversary events and considering how
many of them are small-group team-vs-team things (Catch the Patchmari,
Starwatch). I slept on these the first time through and playing them now I’m
surprised at how much I’m enjoying them, particularly StarWatch despite some of
its dumber ideas (like the AI-controlled Doomfist). This has cemented my
previous opinion:&lt;/p&gt;

&lt;p&gt;OW2 should’ve been 4v4 (2 DPS, 1 tank, 1 healer) with some kind of
fast-respawning mechanic for the healer, and a standard double-team mode (8v8)
either in quickplay or the arcade or something&lt;/p&gt;

&lt;p&gt;Then the number of heroes and the popularity of the roles reflects the number in
play - there are twice as many DPS heroes to choose from, twice as many DPS
players, and twice as many on the team.&lt;/p&gt;</description><author>Pxtl.ca</author><pubDate>Sun, 24 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">http://pxtl.ca/2023/09/24/overwatch2-4v4/</guid></item><item><title>Ghost in the Shell (2017)</title><link>https://jdsalaro.com/bookmark/movie/ghost-in-the-shell-2017/</link><description>Read more on Wikipedia</description><author>Jayson Salazar Rodriguez | @jdsalaro | Blog</author><pubDate>Sun, 24 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jdsalaro.com/bookmark/movie/ghost-in-the-shell-2017/</guid></item><item><title>Choose Postgres queue technology</title><link>https://adriano.fyi/posts/2023-09-24-choose-postgres-queue-technology/</link><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Postgres queue tech is a thing of beauty, but far from mainstream. Its relative obscurity is partially attributable to the &lt;a href="https://en.wikipedia.org/wiki/Cargo_cult"&gt;cargo cult&lt;/a&gt; of &amp;ldquo;scale&amp;rdquo;. The scalability cult has decreed that there are several queue technologies with greater &amp;ldquo;scalability&amp;rdquo; than Postgres, and for that reason alone, Postgres isn&amp;rsquo;t suitably scalable for anyone&amp;rsquo;s queuing needs. The cult of scalability would rather we build applications that scale beyond our wildest dreams than applications that meet concrete customer and business needs. Postgres&amp;rsquo; operational simplicity be damned; scale first, operate later.&lt;/p&gt;
&lt;p&gt;Yet some intrepid technologists, such as those at &lt;a href="https://webapp.io/blog/postgres-is-the-answer/"&gt;webapp.io&lt;/a&gt; have risked excommunication &amp;ndash; their product relies on Postgres queues for core functionality. Companies like webapp.io are an exception to the norm, recognizing that sometimes other principles outweigh &amp;ldquo;scalability&amp;rdquo;. When the cult of scalability fractures, the fractures are often small, but they congeal around new principles like operational simplicity, maintainability, understandability, and familiarity. Sometimes they congeal around new ideas like reusing old tech in &lt;a href="https://fly.io/blog/all-in-on-sqlite-litestream/"&gt;novel ways&lt;/a&gt;, or using Postgres for queues. You, too, should dare risking excommunication from the cult of scalability.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://news.ycombinator.com/item?id=37636841"&gt;Hacker news discussion&lt;/a&gt;
&lt;a href="https://lobste.rs/s/rk3eft/choose_postgres_queue_technology"&gt;Lobste.rs discussion&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="what-is-postgres-queue-tech"&gt;What is Postgres queue tech?&lt;/h2&gt;
&lt;p&gt;Postgres queue tech consists of two parts: announcing and listening for new jobs (pub/sub) and mutual exclusion (row locks). Both are provided out-of-the-box since Postgres 9.5, released in 2016.&lt;/p&gt;
&lt;p&gt;By combining &lt;code&gt;NOTIFY&lt;/code&gt; and &lt;code&gt;LISTEN&lt;/code&gt;, Postgres makes adding pub/sub to any application trivial. In addition to pub/sub, Postgres also provides one-job-per-worker semantics with &lt;code&gt;FOR UPDATE SKIP LOCKED&lt;/code&gt;. Queries with this suffix acquire &lt;a href="https://www.postgresql.org/docs/current/explicit-locking.html#LOCKING-ROWS"&gt;row locks&lt;/a&gt; on matching records, and ignore any records for which locks are already held. Applied to &lt;code&gt;job&lt;/code&gt; records, this feature enables simple queue processing queries, e.g. &lt;code&gt;SELECT * FROM jobs ORDER BY created_at FOR UPDATE SKIP LOCKED LIMIT 1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Combined, these two features form the basis for resource-efficient queue processing. Importantly &lt;code&gt;SKIP LOCKED&lt;/code&gt; provides an &amp;ldquo;inconsistent&amp;rdquo; view of one&amp;rsquo;s data. That inconsistency is exactly what is needed from a queue; jobs already being processed (i.e. row-locked) are invisible to other workers, offering distributed mutual exclusion. These locks pave the way for both periodic batch processing, and real-time job processing by &lt;code&gt;NOTIFY&lt;/code&gt;ing &lt;code&gt;LISTEN&lt;/code&gt;ers of new jobs.&lt;/p&gt;
&lt;p&gt;Despite these Postgres features having many users, there are relatively few public advocates for combining them as a queue backend. For example, &lt;a href="https://news.ycombinator.com/item?id=20022572"&gt;this Hacker News comment&lt;/a&gt; stated that using Postgres this way is &amp;ldquo;hacky&amp;rdquo; and the commenter received no pushback. I found the comment to be load of BS and &lt;a href="https://en.wikipedia.org/wiki/Straw_man"&gt;straw man arguments&lt;/a&gt;. This thinking seems to be &amp;ldquo;the prevailing wisdom&amp;rdquo; of the industry &amp;ndash; if you want to talk about queue technology in public, it better not be a relational database. This industry of cargo cults has little appetite for pushing back on whatever wisdom is &amp;ldquo;prevailing&amp;rdquo;. I hope to disabuse anyone of the notion that Postgres is an inferior queue technology.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll use &amp;ldquo;background jobs&amp;rdquo; as the pretext for this discussion since adding background job processing to applications is a common decision made by developers, which can have far-reaching implications for system maintenance burden. We can think of &amp;ldquo;background jobs&amp;rdquo; as any sort of potentially long-running task such as &amp;ldquo;generate a report and email it to a customer&amp;rdquo;, or &amp;ldquo;process an image and convert it to several other formats&amp;rdquo;. These sorts of use cases generally necessitate queues.&lt;/p&gt;
&lt;h2 id="the-background-job-landscape"&gt;The background job landscape&lt;/h2&gt;
&lt;p&gt;Like all technology decisions, choosing how to process long-running tasks is a choice with many tradeoffs. In the past decade, the tech industry has seemingly come to a consensus that there are a few good tools for queuing long-running tasks for processing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://redis.io/"&gt;Redis&lt;/a&gt; is a wonderful in-memory data store and &amp;ldquo;message broker&amp;rdquo;^ that is the backend for many popular background job libraries&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kafka.apache.org/"&gt;Apache Kafka&lt;/a&gt; A distributed &amp;ldquo;event streaming platform&amp;rdquo; maintained by the Apache Foundation&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rabbitmq.com/"&gt;RabbitMQ&lt;/a&gt; Allegedly the most widely deployed &amp;ldquo;message broker&amp;rdquo;^&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/sqs/"&gt;Amazon SQS&lt;/a&gt; An Amazon SaaS product for highly scalable queues&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My apologies if I&amp;rsquo;ve excluded your favorite(s); this is not meant to be exhaustive.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;^ &amp;ldquo;Message broker&amp;rdquo; simply means that a queue system does other fancy stuff on top of being a queue, but for our discussion, let&amp;rsquo;s consider message brokers queues. There are multiple words and phrases that I consider effectively synonymous with &amp;ldquo;queue&amp;rdquo; and &amp;ldquo;queue processing&amp;rdquo;: &amp;ldquo;message broker(ing)&amp;rdquo;, &amp;ldquo;stream processing&amp;rdquo;, &amp;ldquo;streaming data&amp;rdquo;, etc. I&amp;rsquo;m aware that these mean specific things that are not exactly &amp;ldquo;queue&amp;rdquo; or &amp;ldquo;queue processing&amp;rdquo;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I think it&amp;rsquo;s important to pause here and discuss Redis&amp;rsquo; significance in the world of &amp;ldquo;background jobs&amp;rdquo;. If you browse the &lt;a href="https://github.com/topics/background-jobs"&gt;background jobs GitHub topic&lt;/a&gt;, the top five most popular libraries are all backed by Redis:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/sidekiq/sidekiq"&gt;Sidekiq&lt;/a&gt; (ruby)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/resque/resque"&gt;resque&lt;/a&gt; (ruby)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rq/rq"&gt;rq&lt;/a&gt; (python)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/HangfireIO/Hangfire"&gt;Hangfire&lt;/a&gt; (C#)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/hibiken/asynq"&gt;asynq&lt;/a&gt; (go)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There&amp;rsquo;s a reason for this; because Redis stores data in memory, both its insertion and retrieval speed are phenomenal. It also has a pub-sub API built in, and with native &lt;code&gt;list&lt;/code&gt; and &lt;code&gt;set&lt;/code&gt; data structure which, when combined, &lt;a href="https://redis.com/glossary/redis-queue/"&gt;make for a fantastic queue&lt;/a&gt;. Redis &lt;em&gt;scales&lt;/em&gt;. For many developers, that scalability has made it the default choice, and defaults are profoundly powerful.&lt;/p&gt;
&lt;p&gt;But before choosing Redis because it scales well, consider this quote from Ben Johnson&amp;rsquo;s &lt;a href="https://fly.io/blog/all-in-on-sqlite-litestream/"&gt;I&amp;rsquo;m All-In on Server-Side SQLite&lt;/a&gt;. It&amp;rsquo;s specifically talking about database scalability, but the statement holds for scaling all sorts of infrastructure, like queues:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When we think about new database architectures, we’re hypnotized by scaling limits. If it can’t handle petabytes, or at least terabytes, it’s not in the conversation. But most applications will never see a terabyte of data, even if they’re successful. We’re using jackhammers to drive finish nails.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As an industry, we&amp;rsquo;ve become absolutely obsessed with &amp;ldquo;scale&amp;rdquo;. Seemingly at the expense of all else, like simplicity, ease of maintenance, and reducing developer cognitive load. We all want to believe that we&amp;rsquo;re building the next thing that will demand Google, Facebook, or Uber scale, but the fact is, we&amp;rsquo;re almost always &amp;ndash; not. Our technology decisions should reflect that fact. We&amp;rsquo;re more likely building for relatively small scale, and should be optimizing our decisions around a completely different set of factors that have more to do with team composition than technological superiority.&lt;/p&gt;
&lt;p&gt;When we&amp;rsquo;re starting projects and businesses, we should be optimizing for everything &lt;em&gt;but&lt;/em&gt; scale at the outset. Of course, we don&amp;rsquo;t want to back ourselves into a corner with technology decisions, but we also don&amp;rsquo;t want to build Kubernetes clusters to serve marketing sites for products that are likely to fail for every reason &lt;em&gt;but&lt;/em&gt; the fact that they don&amp;rsquo;t scale well. We should be thinking about what technologies we &lt;em&gt;know well&lt;/em&gt;, what is &lt;em&gt;good enough&lt;/em&gt;, and what is the &lt;em&gt;least toilsome&lt;/em&gt; solution that meets user needs and our team&amp;rsquo;s skill sets. Be proud of choosing &amp;ldquo;good enough&amp;rdquo; over &amp;ldquo;the best&amp;rdquo;; sometimes &amp;ldquo;the best&amp;rdquo; is simply a more difficult path to inevitable failure. List in your head every product that failed because it couldn&amp;rsquo;t scale. There&amp;rsquo;s a much longer list of products that failed long before they needed to.&lt;/p&gt;
&lt;p&gt;What hasn&amp;rsquo;t been said yet, is that Postgres _actually does scale well. But Postgres is general-purpose software, and it&amp;rsquo;s not going to be &amp;ldquo;the best&amp;rdquo; at scaling for queue use cases. It&amp;rsquo;s going to perform pretty well for that use case, just like it performs pretty well doing everything that it does.&lt;/p&gt;
&lt;h2 id="making-decisions"&gt;Making decisions&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re here and feel like you&amp;rsquo;ve seen enough of what I have to say, feel free to abandon this page and scroll through Dan McKinley&amp;rsquo;s &lt;a href="https://boringtechnology.club/"&gt;choose boring technology&lt;/a&gt; slide deck. I&amp;rsquo;m confident that whether you finish this post or Dan&amp;rsquo;s slide deck, you&amp;rsquo;ll make similar decisions when it comes to your next queue technology choice. After all, Dan&amp;rsquo;s &amp;ldquo;Choose Boring Technology&amp;rdquo; talk was the inspiration for this post&amp;rsquo;s title.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;The most important question to ask when making technology decisions is: what technologies am I currently using and understand well?&lt;/p&gt;
&lt;p&gt;The answer to this question informs the &amp;ldquo;cost&amp;rdquo; of choosing technologies for your software stack. Technologies already in use are, presumably, cheap. Assuming they&amp;rsquo;re well understood.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a good chance that you&amp;rsquo;re already using a relational database, and if that relational database is Postgres, you should consider it for queues before any other software. If you&amp;rsquo;re not using Postgres, you should consider whatever is the most boring technology to you, before considering anything else.&lt;/p&gt;

  &lt;figure class="center"&gt;
    &lt;img alt="making technology choices" src="https://adriano.fyi/posts/2023-09-24-choose-postgres-queue-technology//img/choose_boring_tech_cost1.jpeg" style="border-radius: 8px;" /&gt;
    
      &lt;figcaption class="center"&gt;When costs are low, choose any. Source: https://boringtechnology.club&lt;/figcaption&gt;
    
  &lt;/figure&gt;


&lt;p&gt;Technologies not (yet) in use are more expensive.&lt;/p&gt;

  &lt;figure class="center"&gt;
    &lt;img alt="making technology choices" src="https://adriano.fyi/posts/2023-09-24-choose-postgres-queue-technology//img/choose_boring_tech_cost2.jpeg" style="border-radius: 8px;" /&gt;
    
      &lt;figcaption class="center"&gt;When costs are high, choose few. Source: https://boringtechnology.club&lt;/figcaption&gt;
    
  &lt;/figure&gt;


&lt;p&gt;In other words, boring technology is relative to what is already in use. Applications that are oriented around message-passing, like notification systems, might consider RabbitMQ boring technology. Caching applications might consider Redis boring technology. Applications with a large amount of relational data might consider Postgres boring technology. The maximally boring choice is likely the right one for you and your team.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re not already using Redis, Kafka, RabbitMQ, or SQS, adopting any one of them &lt;em&gt;only&lt;/em&gt; for background jobs is expensive. You&amp;rsquo;re adding a new system dependency to every development, test, and production environment, likely for the rest of the application&amp;rsquo;s life. A new set of skills is now required of every future Developer, DBA, and/or SRE role on the team. Now they need to know these new systems&amp;rsquo; intricate failure modes and configuration knobs. Job candidates must be convinced that learning this new technology is a worthwhile time investment. DBAs/SREs need to know how to recover from operational failure, diagnose problems, and monitor performance. There&amp;rsquo;s a lot to know; and there&amp;rsquo;s a lot that nobody on the team realizes they need to know. These systems&amp;rsquo; &lt;a href="https://en.wikipedia.org/wiki/There_are_unknown_unknowns"&gt;unknown unknowns&lt;/a&gt; are a risk. Especially if these systems are a default choice for you, and you haven&amp;rsquo;t put a lot of though into &lt;em&gt;why&lt;/em&gt; they&amp;rsquo;re your default choice.&lt;/p&gt;
&lt;p&gt;This is not all to say that the most boring technology is a panacea &amp;ndash; Postgres included. What one gives up for familiarity, known failure modes, and amortized &amp;ldquo;cost&amp;rdquo; might be paid for in performance, or some other vital principle. After all, pushing and popping from a Postgres queue is considerably slower than Redis. Using Postgres for queues may mean that instead of having a single relational database on a single server, applications now require an &amp;ldquo;application&amp;rdquo; database and a &amp;ldquo;queue&amp;rdquo; database. It may even mean an entirely separate database server for background jobs, so background jobs are independently scalable. It may mean databases need to be &lt;code&gt;VACUUM&lt;/code&gt;ed more frequently, incurring a performance hit in the process. There are &lt;em&gt;many&lt;/em&gt; implications that one should consider before adopting Postgres for queues, and they should be weighed against team and application needs so that an informed decision can be made. Postgres shouldn&amp;rsquo;t be a &lt;em&gt;default&lt;/em&gt; choice. Similarly, neither should Redis, Kafka, RabbitMQ, SQS, or any other distributed queue. Choosing boring technology should be one&amp;rsquo;s default choice.&lt;/p&gt;
&lt;p&gt;Technology choices are &lt;a href="https://en.wikipedia.org/wiki/Turtles_all_the_way_down"&gt;tradeoffs all the way down&lt;/a&gt;. I found that &lt;a href="https://dagster.io/blog/skip-kafka-use-postgres-message-queue#how-we-measured"&gt;Dagster had a pragmatic approach&lt;/a&gt; to adopting Postgres for their queues. When in doubt, consider the following an axiom:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If and only if boring technology is provably unable to meet demands should alternatives be considered.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="build-with-escape-hatches"&gt;Build with escape hatches&lt;/h2&gt;
&lt;p&gt;Earlier I mentioned &amp;ldquo;not getting backed into a corner&amp;rdquo;. With respect to background jobs, that means application code for processing jobs should be queue-agnostic.&lt;/p&gt;
&lt;p&gt;One day&amp;rsquo;s cutting edge tech is another day&amp;rsquo;s boring tech. As applications grow and success is achieved, new technologies tend to get bolted on to applications out of necessity. It&amp;rsquo;s common to add &lt;a href="https://www.memcached.org/"&gt;memcached&lt;/a&gt; or Redis as caching layers (but do consider Postgres &lt;a href="https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-UNLOGGED"&gt;unlogged tables&lt;/a&gt; first!). That means these technologies become &amp;ldquo;boring&amp;rdquo; over time, reducing their cost, and changing the calculus for using them as queues.&lt;/p&gt;
&lt;p&gt;Building with escape hatches is all about abstraction. Earlier I listed the top five most popular background job libraries on GitHub. Except for &lt;a href="https://github.com/HangfireIO/Hangfire"&gt;Hangfire&lt;/a&gt;, none of those libraries provide an escape hatch to queue technologies other than Redis. That means switching queues requires rewriting application code because there&amp;rsquo;s no robust abstraction in front of the underlying queue.&lt;/p&gt;
&lt;p&gt;It shouldn&amp;rsquo;t be that way. Queue tech should be abstracted away, so users can choose the right queue for the job. I&amp;rsquo;m not a Hangfire (or C#) user, but Hangfire appears to have gotten the abstraction right.&lt;/p&gt;
&lt;p&gt;It was with the preceding philosophy of choosing boring tech and building with escape hatches that I built Neoq &lt;a href="https://github.com/acaloiaro/neoq"&gt;https://github.com/acaloiaro/neoq&lt;/a&gt;. Neoq queues can be in-memory, Postgres, or Redis (contributions for your favored boring tech welcome!). Users can switch between queues without changing any application code &amp;ndash; simply initialize it with a different queue backend. Neoq is more abstraction than it is concrete implementation. While both the in-memory and Postgres implementations are first-party, the Redis implementation is &lt;a href="https://github.com/hibiken/asynq"&gt;asynq&lt;/a&gt;. It&amp;rsquo;s more about providing escape hatches than locking developers into a specific underlying queue technology.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d love to see more neoq-like libraries for languages other than Go. I think the lack of software libraries with escape hatches is what backs a lot of developers into a corner, forcing them to begin simple projects with a Redis dependency, long before Redis is warranted. Redis is fantastic, but it&amp;rsquo;s not always the right queue, or right amount of complexity for the job. The same goes for Kafka, RabbitMQ, and SQS.&lt;/p&gt;
&lt;h2 id="choosing-postgres-queue-tech"&gt;Choosing Postgres queue tech&lt;/h2&gt;
&lt;p&gt;I hope this post encourages others to risk excommunication from the cult of scale the next time they&amp;rsquo;re choosing queue technology. There are so many important principles that are not &amp;ldquo;scale&amp;rdquo; to consider when choosing technologies. Make boring technology your default choice, and choose Postgres if it bores you.&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;</description><author>Adriano Caloiaro's personal blog</author><pubDate>Sun, 24 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://adriano.fyi/posts/2023-09-24-choose-postgres-queue-technology/</guid></item><item><title>Analyzing Ethereum with Cryo</title><link>https://mteam.space/posts/analyzing-ethereum-with-cryo/</link><description>In this article, I install Cryo, download some Ethereum data, and analyze it with graphs and charts.</description><author>mteam's Writings</author><pubDate>Sun, 24 Sep 2023 01:05:47 GMT</pubDate><guid isPermaLink="true">https://mteam.space/posts/analyzing-ethereum-with-cryo/</guid></item><item><title>Ukulele chords table</title><link>http://localhost:4000/2023/09/24/ukulele-chords-table.html</link><description>Ukulele chords table</description><author>ms75</author><pubDate>Sun, 24 Sep 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">http://localhost:4000/2023/09/24/ukulele-chords-table.html</guid></item><item><title>Benchmarking is Hard: Caddy vs Nginx Edition</title><link>https://www.patrickdap.com/post/benchmarking-is-hard/</link><description>&lt;p&gt;&lt;em&gt;Or how an evening on Twitter/X took me down the rabbit hole&lt;/em&gt;&lt;/p&gt;&lt;img alt="Benchmarking is Hard: Caddy vs Nginx Edition" height="150" src="https://www.patrickdap.com/assets/benchmarks.c51E7ujt_oM591.jpg" style="float: right; margin: 0 0 12px 18px; border-radius: 8px;" width="150" /&gt;&lt;p&gt;Back in November 2022 I was browsign the then Twittosphere (now Exosphere?) when I ran across an interesting tweet from @manishrjain: It caught my attention, but not because of the claim that Caddy outperforms Nginx by 2x, but instead because often, when it comes to benchmarking and comparing technologies, especially when it comes to Servers, Internet and Network Requests, you could often overlook specific details. Interestingly enough, the claim then went from 2x to 4x more performance! Moar performance! Now, trying to give credit where credit is due, I said that the numbers do look impressive, but Nginx is a good old dog that has seen a lot of progress on what we know of the Internet today and while it isn't written in Go, it was written in C, which is a pretty fast language anyways, so...&lt;/p&gt;&lt;div style="clear: both;"&gt;&lt;/div&gt;</description><author>Patrick D'appollonio</author><pubDate>Sat, 23 Sep 2023 17:20:41 GMT</pubDate><guid isPermaLink="true">https://www.patrickdap.com/post/benchmarking-is-hard/</guid></item><item><title>Letting go is hard</title><link>https://ilearnt.com/blog/lettinggo/</link><description>&lt;p&gt;Last weekend our daughter started on the next part of her life journey. We dropped her off at Exeter University as she is about to embark on a four year adventure studying psychology.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Sat, 23 Sep 2023 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/lettinggo/</guid></item><item><title>The Photo Game: Man Made</title><link>https://andrewbridges.org/the-photo-game-man-made/</link><description>Last weekend, my dad and I rode our motorcycles to Black Hawk, Colorado.</description><author>Show Notes</author><pubDate>Sat, 23 Sep 2023 06:25:17 GMT</pubDate><guid isPermaLink="true">https://andrewbridges.org/the-photo-game-man-made/</guid></item><item><title>Project Panthalia: Adding speech to text for mobile blogging</title><link>https://zackproser.com/videos/panthalia-speech-to-text</link><description>I added speech-to-text capabilities to Panthalia, so that I can commence blog posts more quickly than ever before, from anywhere</description><author>Zachary Proser</author><pubDate>Sat, 23 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/videos/panthalia-speech-to-text</guid></item><item><title>A Disk Usage Mystery</title><link>https://www.marginalia.nu/log/89-disk-usage-mystery/</link><description>&lt;p&gt;I ran into a bit of a puzzling situation yesterday, testing some of the new index construction changes before they&amp;rsquo;re going live in a few days.&lt;/p&gt;
&lt;p&gt;The process crashed with a pretty non-descript stack trace complaining about illegal instructions, so first glance it looked more like it was within the realm of freak JVM bug, cosmic ray, hardware error maybe.&lt;/p&gt;
&lt;p&gt;I was doing this on my developer workstation, which also spawned a popup complaining that the hard drive it was working on had nearly run out of space, and inferred that the error probably was due to memory mapping more space onto a disk than what was possible.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Sat, 23 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/89-disk-usage-mystery/</guid></item><item><title>Quantified Kilimanjaro (or Procrastination)</title><link>/hiking/2023/09/quantified-kilimanjaro.html</link><description>&lt;p&gt;I climbed Kilimanjaro via the Rongai route in October of 2012, capturing runkeeper, and other metrics all along the way. I’ve also been meaning to write this post since then and it’s taken over 10 years to finally overcome procrastination and write up how I logged my data up to and down from the summit.&lt;/p&gt;

&lt;h2 id="planning---what-route-when-and-what-coordinator"&gt;Planning - what route, when, and what coordinator&lt;/h2&gt;

&lt;p&gt;I think I first wanted to climb Kilimanjaro when I heard about it in Toto’s Africa song. It seemed pretty cool as the tallest single standing mountain in Africa and just mysterious as this giant mountain sticking up out of the wilds. And then as I aged, it seemed part of culture with &lt;a href="https://en.wikipedia.org/wiki/The_Snows_of_Kilimanjaro_(short_story)"&gt;Hemingway’s Snows of Kilimanjaro&lt;/a&gt; and just every time I heard about it, it seemed like an amazing thing to do, like visit &lt;a href="https://en.wikipedia.org/wiki/Shangri-La"&gt;Shangri-La&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I love hiking, but am pretty weak and not willing to train for real mountain climbing. I learned tht Kilimanjaro is over 19,000 feet but doesn’t require any technical training or special equipment so it seemed like something that might be achievable. And one night, while hiking the Inca Trail with my uncle John and friend Calros, we started talking about “wouldn’t it be cool to do this with Kilimanjaro.” That was in 2006, and in 2012, John and I went on another trip and summited Kilimanjaro. So it’s definitely doable for a tourist.&lt;/p&gt;

&lt;p&gt;Researching Kilimanjaro, I read quite a bit. I relied mostly on some guidebooks, mostly &lt;a href="https://www.amazon.com/gp/product/1852844132/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&amp;amp;psc=1"&gt;Kilimanjaro: A Trekker’s Guide by Alex Steward (2004, Ciccerone)&lt;/a&gt; and &lt;a href="https://www.amazon.com/Kilimanjaro-All-one-Kilimanjaro-Trailblazer/dp/1905864957"&gt;Kilimanjaro - The Trekking Guide to Africa’s Highest Mountain by Henry Stedman&lt;/a&gt;, and I also remember reading &lt;a href="https://www.amazon.com/gp/product/1566567815/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&amp;amp;psc=1"&gt;Michael Moushabeck’s Kilimanjaro: A Photographic Journey to the Roof of Africa&lt;/a&gt; that wasn’t very useful but had many nice photos. I think I like books over web sites because of the editing and density of information vs filtering through tons of web pages.&lt;/p&gt;

&lt;h3 id="route"&gt;Route&lt;/h3&gt;

&lt;p&gt;Kilimanjaro is really popular so there are many &lt;a href="https://en.wikipedia.org/wiki/Mount_Kilimanjaro_climbing_routes"&gt;different routes&lt;/a&gt; to take:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Rongai - less busy, and has more time at each altitude for acclimatization - ended up selecting this one for 7 days because of acclimatization, this is usually 6 days but we added an extra day to acclimatize with our tour operator&lt;/li&gt;
  &lt;li&gt;Marangu - most popular, sleeping in huts, steeper climb - ended up not picking this because it had a lower success rate and seemed like it would be more crowded&lt;/li&gt;
  &lt;li&gt;Macheme - second most popular, I think people call this the “whiskey” route as it’s supposed to be hard&lt;/li&gt;
  &lt;li&gt;Lemosho - considered this because it’s also longest at 8 days, but ended up not taking it because the extra day didn’t work out with my work’s vacation schedule, and it had some driving and drop off rather than all hiking&lt;/li&gt;
  &lt;li&gt;Umbwe - shortest and quickest route&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There’s a new route now called “Northern Circuit” that I don’t remember that is 9 days so I didn’t consider that in our planning.&lt;/p&gt;

&lt;p&gt;I think we picked Rongai because it was kind of in a goldilocks zone of not being too long and not too short. My biggest concern was acclimatization because I read that’s the biggest reason for failure so this seemed like a good route to gain altitude slowly.&lt;/p&gt;

&lt;p&gt;Joseph Mosha, my guide, handed me a copy of this hand drawn map and it was like my bible during the trek. I love it so much and have it framed on my wall.&lt;/p&gt;

&lt;p&gt;&lt;img alt="Route map drawn by my guide Joseph Mosha" src="/assets/images/kilimanjaro_rongai_route.jpg" /&gt;&lt;/p&gt;

&lt;h3 id="coordinator"&gt;Coordinator&lt;/h3&gt;

&lt;p&gt;It’s possible to climb Kilimanjaro on your own. While I wanted to be cost conscious, I still had a pretty open window and budget of when to go, so I wanted to find a tour operator. The trip is offered by lots of travel groups but I think most resell programs or have remote people scheduling local operators.&lt;/p&gt;

&lt;p&gt;I wanted to make sure the operator was locally based to both support Tanzania people as well as to make sure porters and guides were treated ethically and paid. I wanted to use the &lt;a href="https://www.tailormadeafrica.com/mount-kilimanjaro/the-african-walking-company/"&gt;African Walking Company&lt;/a&gt; but didn’t find a way to book directly through them so ended up booking through &lt;a href="https://peakplanet.com/"&gt;PeakPlanet&lt;/a&gt; to get to them. Back in 2012, I paid $2450 for the 7 day rongai. This was the price to add an extra day to their advertised 6-day trip. It looks like the now offer an &lt;a href="https://peakplanet.com/kilimanjaro-group-dates-prices/"&gt;9-day rongai for about $3k&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is for a small group trip where I think there was a max size of 8 people or something and they guaranteed the departure dates. There were cheaper operators if you had more flexible dates or could handle cancellations.&lt;/p&gt;

&lt;p&gt;I also made sure this included airport transfers and shoulder lodging and wanted to pay a single price to handle all the “incidentals.” So this price included pickup from Arusha (JRO) airport, transport to Moshi and staying in their lodge before and after the trek.&lt;/p&gt;

&lt;p&gt;This ended up being really important because there were only three people on our trek and other operators would have delayed or cancelled. PeakPlanet/AWS said that they would definitely depart and return on our selected dates and they stuck to that.&lt;/p&gt;

&lt;p&gt;Using local staff was really important to us because I thought that Tanzanians would be more knowledgeable and all guides and porters must be local, so if non-Tanzanians are involved those are extra people beyond the actual people carrying out the trek. And PeakPlanet/AWS was really great in that we only met with and worked with Tanzanians and the two guides we had - Joseph Mosha and Elija Joel - were very experienced (dozens of summits), knowledgeable about climbing and mountain sickness, and very friendly and caring. I think Elija is the single most important reason I was able to complete and without his help, I don’t think I would make it.&lt;/p&gt;

&lt;p&gt;PeakPlanet did have a lot of guidance on equipment, training, and general information that made preparing for the trip easier.&lt;/p&gt;

&lt;p&gt;Here’s the route map they shared for Rongai…&lt;/p&gt;

&lt;p&gt;&lt;img alt="PeakPlanet Rongai Route" src="/assets/images/kilimanjaro_rongai_from_peak_planet.png" /&gt;&lt;/p&gt;

&lt;h3 id="when-to-go"&gt;When to go&lt;/h3&gt;

&lt;p&gt;I wanted to visit Kilimanjaro before the snows were gone and but wanted to find the best weather possible. I ended up picking late September 28th through October 6th to hit the end of the rainy season and also near a full moon on summit. Weather ended up being great and we didn’t have any rain at all. And I saw almost no snow as the trail was dry even up to the summit.&lt;/p&gt;

&lt;h3 id="equipment"&gt;Equipment&lt;/h3&gt;

&lt;p&gt;The Stedman book was super helpful for all equipment purchases. I think I booked my trip and flight in February, so had about six months to shop for deals on equipment.&lt;/p&gt;

&lt;p&gt;My basic plan was to have multiple layers, travel light, and have waterproof stuff just in case. Porters carried my bad so I had a 30 pound limit on that and just a day bag that I carried.&lt;/p&gt;

&lt;p&gt;I rented my sleeping bag and tent from the operator, but ended up bringing the following gear:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Zamberlan boots - this is probably the most important item and I spent the most time. I considered Saloman, Asolo, Lowa, and every boot sold in Atlanta and online. I went with Zamberlan because of the fit, reputation, and features. I went with the &lt;a href="https://www.zamberlanusa.com/product/9789427/996-vioz-gtx-r-men-s-hiking-backpacking-boots-dark-grey"&gt;996 Vioz GTX&lt;/a&gt; as they were waterproof with goretex, leather, vibram soles, and a toe box that fit well with me. The waterproof ended up being unnecessary as I never experience snow, but they have been really handy since then going through streams and puddles and never worrying. I still wear them on week-end hikes and they are almost done. I’d guess I have over 1,000 miles over 12 years and have definitely got my money’s worth. I think I spent $300. Also, I have the original laces and they’ve held up really well. I spent about six months breaking them in on local trails and hikes.&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.icebreaker.com/en-us/tshirts"&gt;Icebreaker merino wool t-shirts&lt;/a&gt; x 2 - these were amazing for both warmth as well as durability. I alternated them each day and they were still not funky by the end of the trip. They are itchy the first day but afterwards very comfortable. I still wear them 11 years later.&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.icebreaker.com/en-us/mens-baselayers/merino-260-tech-long-sleeve-half-zip-thermal-top/104372U2.html?dwvar_104372U2_color=865&amp;amp;dwvar_104372U2_US=in_line"&gt;Icebreaker 260 thermal top&lt;/a&gt; and &lt;a href="https://www.icebreaker.com/en-us/mens-baselayers/merino-260-tech-thermal-leggings-with-fly/104374U2.html?dwvar_104374U2_color=001&amp;amp;dwvar_104374U2_US=in_line"&gt;leggings&lt;/a&gt;. My top has a hood though that fits snugly with just the face exposed. Icebreaker didn’t have as many options back then and I was able to get them at REI on sale. I wore these on the last few days when it got cold and they were really helpful.&lt;/li&gt;
  &lt;li&gt;Smartwool socks (x3) and underwear (x3). More merino and I alternated these each day. I think my base layer was all merino and it worked out pretty well.&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.patagonia.com/product/mens-ultralight-down-jacket/84757.html"&gt;Patagonia Ultralight Down Hoody&lt;/a&gt; - this was very light at 900 grams and packed into its own little bag. I wore it at camp at night and on the summit when I pretty much wore everything&lt;/li&gt;
  &lt;li&gt;1-2 fleece mid layers that I don’t remember them. Comically I think they were lululemon or REI.&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://arcteryx.com/us/en/shop/venta-ar-glove"&gt;Arc’teryx Zenta AR Glove&lt;/a&gt; - these looked cool and also lasted, but my fingers were so cold on summit, even wearing inner wool liner gloves. I would buy something else instead.&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://leaf.arcteryx.com/us/en/shop/mens/alpha-lt-jacket-gen-2"&gt;Arc’teryx Alpha LT Hard Shell&lt;/a&gt; - I think this was the most expensive item I bought, but I liked it. It was extremely light so I carried it in my day bag even from the beginning. It provided no warmth but completely stopped wind when worn over my layers and had vents under the arms I opened up to help regulate heat. I still have it and wear it frequently so I think it’s a decent buy. I think I bought an XL and wish I had bought an XXL because I end up wearing it over everything else. I also bought a soft shell and ended up not wearing it because I just put this over my layers.&lt;/li&gt;
  &lt;li&gt;Camelbak Tycoon - 3 liter water capacity with 20 liters dry storage. This had insulation for the water bladder and drinking tube. Camelbak doesn’t make this any more but it’s great and I still use it for all my hikes. Camelbak support has been amazing too, the clip across my chest that holds the straps in place broke and camelbak sent me a new one free.&lt;/li&gt;
  &lt;li&gt;I brought waterproof outer pants that I don’t remember the brand. I never had to use them.&lt;/li&gt;
  &lt;li&gt;Nomad GoalZero solar kit and battery - this was really useful for charging my iphone every day. I was able to log every day’s hike and this solar kit for $100 from REI worked out well enough that I just hung the panels on my backpack each day, and charged my phone at night. I had an old iphone with gps.&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://runkeeper.com"&gt;RunKeeper&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Some gps altitude app that shows me altitude based on GPS but didn’t need signal. I can’t remember the name, but used it and screenshot each day. I can’t find the screenshots, but recorded the altitudes into my diary.&lt;/li&gt;
  &lt;li&gt;Moleskine Squared Reporter Hardcover (that the don’t make any more).&lt;/li&gt;
  &lt;li&gt;Some astronaut pen that wrote in subzero temperatures, upside down, and under pressure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id="medications-and-vaccinations"&gt;Medications and Vaccinations&lt;/h3&gt;

&lt;p&gt;People debate whether to take Diamox or not. I read about the side effects and they were minimal so I went to a travel clinic for my vaccinations and had them give me a prescription for Diamox. I tested it out for a few days to see if I had any negative effects, and didn’t. So I took it throughout my trip. It did make me urinate a bit more than usual, but that’s not so bad as you want to drink lots of water.&lt;/p&gt;

&lt;p&gt;I also took Malarone to protect against Malaria and took that before leaving the US and after returning. Got the same prescription from my travel clinic. I’ve taken it a few times for other trips since then and haven’t had any negative effects.&lt;/p&gt;

&lt;p&gt;I had a prescription for xanax and took that some nights to help sleep through the screaming animals (monkeys maybe) and cold. Some people advise against this, but I think it was a life saver as it can be hard to sleep when it’s 0 degrees and you have altitude adjustment and are on the side of a mountain.&lt;/p&gt;

&lt;p&gt;My travel clinic gave me the following vaccinations:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Typhoid&lt;/li&gt;
  &lt;li&gt;Hepatitis B&lt;/li&gt;
  &lt;li&gt;Polio&lt;/li&gt;
  &lt;li&gt;Meningococcal meningitis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I already had Hepatitis A and Yellow Fever from a previous trip and they were still good. I also had DPT and MMR.&lt;/p&gt;

&lt;p&gt;CDC recommends Rabies but I didn’t get it as it’s expensive, has multiple courses, and it’s extremely rare to encounter wildlife on Kilimanjaro. If I had gone on a safari, I would have considered it more.&lt;/p&gt;

&lt;h2 id="daily-log"&gt;Daily log&lt;/h2&gt;

&lt;p&gt;I was really into quantified self back in 2011 and 2012 so I wanted to make sure to keep a log each day. I used a fitbit to track my exercise and food and &lt;a href="https://youtu.be/GS1hINtdJKg"&gt;lost 120+ pounds&lt;/a&gt;. And I used RunKeeper to log each hike throughout the day. And kept a paper record in my notebook.&lt;/p&gt;

&lt;p&gt;I didn’t find any detailed logs of the actual hikes, so I thought I would capture it all here in case people wanted to see specific routes, distances, times, altitude, etc. Of course, it’s 11 years later, but then the best time to plant a tree is 20 years ago and the second best is today.&lt;/p&gt;

&lt;p&gt;Runkeeper doesn’t really have a nice way to share or embed these hikes any more, so I’m just screenshotting each one. I’ve used runkeeper forever and it’s nice they keep the data, but I wish they had better exports.&lt;/p&gt;

&lt;p&gt;I flew ATL-&amp;gt;JFK-&amp;gt;AMS-&amp;gt;JRO and arrived on 9/27 and stayed in Kilimanjaro Mountain Resort in Marangu.&lt;/p&gt;

&lt;h3 id="september-28-friday"&gt;September 28, Friday&lt;/h3&gt;

&lt;p&gt;9/28 - stayed in hotel in Marangu with a trip into Moshi for shopping. Prep day to get over jet lag. $80 for the taxi from hotel to Moshi and back. Paid for everything in the trip in US dollars.&lt;/p&gt;

&lt;p&gt;Comically, this is the only day I got a photo of Kilimanjaro looming in the distance. You could see it through the trees from the hotel. I spent the entire trek with Kilimanjaro slowing getting closer and then farther away. I must have looked at it 2,000 times. But never too a picture. Each time looking at it, I was tired and looking at where I was going and never took a photo.&lt;/p&gt;

&lt;p&gt;&lt;img alt="Kilimanjaro trough the trees from Marangu" src="/assets/images/kilimanjaro_in_the_distance.jpg" /&gt;&lt;/p&gt;

&lt;h3 id="september-29-day1-saturday"&gt;September 29 (Day1), Saturday&lt;/h3&gt;

&lt;p&gt;5.54 miles, 641 meters gained. Camped at 2,641 meters.&lt;/p&gt;

&lt;p&gt;Started at 1035 at the base of Rongai route. Made camp at 3pm. Started at 2,000 meters and set camp at 2,641. After camp, took a 45 minute walk up and back for acclimatization.&lt;/p&gt;

&lt;p&gt;4 specific segments for today’s hikes.&lt;/p&gt;

&lt;h4 id="day1-hike1-200-miles-1-hour-14-minutes-finished-at-1215pm"&gt;&lt;a href="http://rnkpr.com/a219aaf"&gt;Day1-Hike1&lt;/a&gt;, 2.00 miles, 1 hour, 14 minutes, finished at 12:15pm&lt;/h4&gt;

&lt;p&gt;&lt;img alt="Day1-Hike1, 2.00 miles, 1 hour, 14 minutes, finished at 12:15pm" src="/assets/images/kilimanjaro_rk_day1_hike1-2023-09-29_1101.png" /&gt;
| &lt;a href="/assets/geo/kilimanjaro_rk_day1_hike1-2023-09-29_1101.gpx"&gt;gpx&lt;/a&gt; | &lt;a href="/assets/geo/kilimanjaro_rk_day1_hike1-2023-09-29_1101.kml"&gt;kml&lt;/a&gt;&lt;/p&gt;

&lt;h4 id="day1-hike2-126-miles-4430-finished-at-154pm"&gt;&lt;a href="http://rnkpr.com/a219aak"&gt;Day1-Hike2&lt;/a&gt;, 1.26 miles, 44:30, finished at 1:54pm&lt;/h4&gt;

&lt;p&gt;&lt;img alt="Day1-Hike2, 1.26 miles, 44:30, finished at 1:54pm" src="/assets/images/kilimanjaro_rk_day1_hike2-2023-09-29_1310.png" /&gt;
| &lt;a href="/assets/geo/kilimanjaro_rk_day1_hike2-2023-09-29_1310.gpx"&gt;gpx&lt;/a&gt; | &lt;a href="/assets/geo/kilimanjaro_rk_day1_hike2-2023-09-29_1310.kml"&gt;kml&lt;/a&gt;&lt;/p&gt;

&lt;h4 id="day1-hike3-106-miles-4342-finished-at-303pm"&gt;&lt;a href="http://rnkpr.com/a219aaq"&gt;Day1-Hike3&lt;/a&gt;, 1.06 miles, 43:42, finished at 3:03pm&lt;/h4&gt;

&lt;p&gt;&lt;img alt="Day1-Hike3, 1.06 miles, 43:42, finished at 3:05pm" src="/assets/images/kilimanjaro_rk_day1_hike3-2023-09-29_1421.png" /&gt;
| &lt;a href="/assets/geo/kilimanjaro_rk_day1_hike3-2023-09-29_1421.gpx"&gt;gpx&lt;/a&gt; | &lt;a href="/assets/geo/kilimanjaro_rk_day1_hike3-2023-09-29_1421.kml"&gt;kml&lt;/a&gt;&lt;/p&gt;

&lt;h4 id="day1-hike4-122-miles-4602-finished-at-418pm"&gt;&lt;a href="http://rnkpr.com/a219ab7"&gt;Day1-Hike4&lt;/a&gt;, 1.22 miles, 46:02, finished at 4:18pm&lt;/h4&gt;

&lt;p&gt;&lt;img alt="Day1-Hike4, 1.22 miles, 46:02, finished at 4:18pm" src="/assets/images/kilimanjaro_rk_day1_hike4-2023-09-29_1532.png" /&gt;
| &lt;a href="/assets/geo/kilimanjaro_rk_day1_hike4-2023-09-29_1532.gpx"&gt;gpx&lt;/a&gt; | &lt;a href="/assets/geo/kilimanjaro_rk_day1_hike4-2023-09-29_1532.kml"&gt;kml&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="september-30-day2-sunday"&gt;September 30 (Day2), Sunday&lt;/h3&gt;

&lt;p&gt;6.92 miles, 1018 meters gained. Camped at 3,659 meters.&lt;/p&gt;

&lt;p&gt;4 segments today as well.&lt;/p&gt;

&lt;h4 id="day2-hike1-173-miles-10529-finished-942am"&gt;&lt;a href="http://rnkpr.com/a219abk"&gt;Day2-Hike1&lt;/a&gt;, 1.73 miles, 1:05:29, finished 9:42am&lt;/h4&gt;

&lt;p&gt;&lt;img alt="Day2-Hike1, 1.73 miles, 1:05:29, finished 9:42am" src="/assets/images/kilimanjaro_rk_day2_hike1-2023-09-30_0837.png" /&gt;
| &lt;a href="/assets/geo/kilimanjaro_rk_day2_hike1-2023-09-30_0837.gpx"&gt;gpx&lt;/a&gt; | &lt;a href="/assets/geo/kilimanjaro_rk_day2_hike1-2023-09-30_0837.kml"&gt;kml&lt;/a&gt;&lt;/p&gt;

&lt;h4 id="day2-hike2-138-miles-14346-finished-1124am"&gt;&lt;a href="http://rnkpr.com/a219ad1"&gt;Day2-Hike2&lt;/a&gt;, 1.38 miles, 1:43:46, finished 11:24am&lt;/h4&gt;

&lt;p&gt;&lt;img alt="Day2-Hike2, 1.38 miles, 1:43:46, finished 11:24am" src="/assets/images/kilimanjaro_rk_day2_hike2-2023-09-30_0951.png" /&gt;
| &lt;a href="/assets/geo/kilimanjaro_rk_day2_hike2-2023-09-30_0951.gpx"&gt;gpx&lt;/a&gt; | &lt;a href="/assets/geo/kilimanjaro_rk_day2_hike2-2023-09-30_0951.kml"&gt;kml&lt;/a&gt;&lt;/p&gt;

&lt;h4 id="day2-hike3-225-miles-13746-finished-257pm"&gt;&lt;a href="http://rnkpr.com/a219ad9"&gt;Day2-Hike3&lt;/a&gt;, 2.25 miles, 1:37:46, finished 2:57pm&lt;/h4&gt;

&lt;p&gt;&lt;img alt="Day2-Hike3, 2.25 miles, 1:37:46, finished 2:57pm" src="/assets/images/kilimanjaro_rk_day2_hike3-2023-09-30_1320.png" /&gt;
| &lt;a href="/assets/geo/kilimanjaro_rk_day2_hike3-2023-09-30_1320.gpx"&gt;gpx&lt;/a&gt; | &lt;a href="/assets/geo/kilimanjaro_rk_day2_hike3-2023-09-30_1320.kml"&gt;kml&lt;/a&gt;&lt;/p&gt;

&lt;h4 id="day2-hike4-156-miles-12100-finished-437pm"&gt;&lt;a href="http://rnkpr.com/a219ade"&gt;Day2-Hike4&lt;/a&gt;, 1.56 miles, 1:21:00, finished 4:37pm&lt;/h4&gt;

&lt;p&gt;&lt;img alt="Day2-Hike3, 1.56 miles, 1:21:00, finished 4:37pm" src="/assets/images/kilimanjaro_rk_day2_hike4-2023-09-30_1516.png" /&gt;
| &lt;a href="/assets/geo/kilimanjaro_rk_day2_hike4-2023-09-30_1516.gpx"&gt;gpx&lt;/a&gt; | &lt;a href="/assets/geo/kilimanjaro_rk_day2_hike4-2023-09-30_1516.kml"&gt;kml&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="october-1-day3-monday"&gt;October 1 (Day3), Monday&lt;/h3&gt;

&lt;p&gt;2.20 miles, 677 meters gained. Camped at 4,336 meters.&lt;/p&gt;

&lt;p&gt;Just a short distance today, but a lot of altitude gained. Camped at Mawenzi Tarn at the base of &lt;a href="https://www.tranquilkilimanjaro.com/places/mawenzi-peak-mount-kilimanjaro/"&gt;Mawenzi Peak&lt;/a&gt;. This is the first day I really started to notice &lt;a href="https://medlineplus.gov/ency/article/000133.htm"&gt;Acute Mountain Sickness (AMS)&lt;/a&gt;. I had “thick thinking” where it would just take a long time to think through things, it took me 3 minutes to put batteries in my headlamp and that would normally take 30 seconds.&lt;/p&gt;

&lt;h4 id="day3-hike1-220-miles-25017-finished-1152am"&gt;&lt;a href="http://rnkpr.com/a219adq"&gt;Day3-Hike1&lt;/a&gt;, 2.20 miles, 2:50:17, finished 11:52am&lt;/h4&gt;

&lt;p&gt;&lt;img alt="Day3-Hike1, 2.20 miles, 2:50:17, finished 11:52am" src="/assets/images/kilimanjaro_rk_day3_hike1-2023-10-01_0902.png" /&gt;
| &lt;a href="/assets/geo/kilimanjaro_rk_day3_hike1-2023-10-01_0902.gpx"&gt;gpx&lt;/a&gt; | &lt;a href="/assets/geo/kilimanjaro_rk_day3_hike1-2023-10-01_0902.kml"&gt;kml&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="october-2-day4-tuesday"&gt;October 2 (Day4), Tuesday&lt;/h3&gt;

&lt;p&gt;1.3 miles, 164 meters gained. Camped at 4,500 meters.&lt;/p&gt;

&lt;p&gt;Stayed at Mawenzi Tarn again and spent the day just hanging out and acclimatizing. This is the only day I touched snow as I went hiked around the base to some standing snow.&lt;/p&gt;

&lt;p&gt;We were above the clouds and it was really beautiful just relaxing after a few days and using lots of sunblock. Also spent time with clouds rolling over us and it was really neat to be inside the cloud as it passed over.&lt;/p&gt;

&lt;p&gt;&lt;img alt="lots of fun playing around above the clouds" src="/assets/images/kilimanjaro_mawenzi_having_fun.jpg" /&gt;&lt;/p&gt;

&lt;h4 id="day4-hike1-131-miles-13939-finished-1045am"&gt;&lt;a href="http://rnkpr.com/a219ae6"&gt;Day4-Hike1&lt;/a&gt;, 1.31 miles, 1:39:39, finished 10:45am&lt;/h4&gt;

&lt;p&gt;&lt;img alt="Day4-Hike1, 1.31 miles, 1:39:39, finished 10:45am" src="/assets/images/kilimanjaro_rk_day4_hike1-2023-10-02_0906.png" /&gt;
| &lt;a href="/assets/geo/kilimanjaro_rk_day4_hike1-2023-10-02_0906.gpx"&gt;gpx&lt;/a&gt; | &lt;a href="/assets/geo/kilimanjaro_rk_day4_hike1-2023-10-02_0906.kml"&gt;kml&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="october-3-day5-wednesday"&gt;October 3 (Day5), Wednesday&lt;/h3&gt;

&lt;p&gt;5.32 miles, 215 meters gained. Camped at 4,715 meters.&lt;/p&gt;

&lt;p&gt;Arrived at Kibo Hut camp “Base camp”. So it took about 21 miles to get here. We arrived pretty early at 1pm. We napped until dinner at 5, then sleep until 11, eat a light meal and start the summit. The it should have taken 6 hours to Gillman’s point, then 2 hours to Uhuru, then 2-4 hours back down. We didn’t actually sleep in the hut, we stayed in our standard tents.&lt;/p&gt;

&lt;h4 id="day5-hike1-532-miles-33054-finished-1116am"&gt;&lt;a href="http://rnkpr.com/a219agk"&gt;Day5-Hike1&lt;/a&gt;, 5.32 miles, 3:30:54, finished 11:16am&lt;/h4&gt;

&lt;p&gt;&lt;img alt="Day5-Hike1, 5.32 miles, 3:30:54, finished 11:16am" src="/assets/images/kilimanjaro_rk_day5_hike1-2023-10-03_0746.png" /&gt;
| &lt;a href="/assets/geo/kilimanjaro_rk_day5_hike1-2023-10-03_0746.gpx"&gt;gpx&lt;/a&gt; | &lt;a href="/assets/geo/kilimanjaro_rk_day5_hike1-2023-10-03_0746.kml"&gt;kml&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="october-4-day6-thursday"&gt;October 4 (Day6), Thursday&lt;/h3&gt;

&lt;p&gt;So technically, I started summit at 11:56pm but include it as day 6.&lt;/p&gt;

&lt;p&gt;3 segments today, but only the first with runkeeper.&lt;/p&gt;

&lt;p&gt;3.50 miles, 1195 meters gained to summit.&lt;/p&gt;

&lt;p&gt;Phone battery died when I pulled it out at summit to end runkeeper and take a picture. Luckily I had it inside my layers and it tracked everything from Kibo hut to Uhuru.&lt;/p&gt;

&lt;p&gt;So what’s interesting is my diary has the max altitude as 5910 meters even though the actual hight is &lt;a href="https://en.wikipedia.org/wiki/Mount_Kilimanjaro"&gt;5895 meters&lt;/a&gt;. So this is wrong, but I kept it in my records as I measured altitudes consistently with the same app so kept this as the altitude for peak assuming that my app would measure wrong, consistently so at least this lets me calculate the altitude gained or lost.&lt;/p&gt;

&lt;p&gt;3.50 miles back down from summit and it took 2 hours. Then napped for an hour and got up to walk about six miles to camp at Horombu Hut, 3,720 meters, so 995 meters lost from last camp.&lt;/p&gt;

&lt;h4 id="day6-hike1-350-miles-71658-finished-712am"&gt;&lt;a href="http://rnkpr.com/a219agy"&gt;Day6-Hike1&lt;/a&gt;, 3.50 miles, 7:16:58, finished 7:12am&lt;/h4&gt;

&lt;p&gt;&lt;img alt="Day6-Hike1, 3.50 miles, 7:16:58, finished 7:12am" src="/assets/images/kilimanjaro_rk_day6_hike1-2023-10-03_2356.png" /&gt;
| &lt;a href="/assets/geo/kilimanjaro_rk_day6_hike1-2023-10-03_2356.gpx"&gt;gpx&lt;/a&gt; | &lt;a href="/assets/geo/kilimanjaro_rk_day6_hike1-2023-10-03_2356.kml"&gt;kml&lt;/a&gt;&lt;/p&gt;

&lt;h4 id="day6-hike2-350-miles-200-finished-916am"&gt;Day6-Hike2, 3.50 miles, ~2:00, finished 9:16am&lt;/h4&gt;

&lt;p&gt;Not sure exactly how long this took, my diary says 2 hours.&lt;/p&gt;

&lt;h4 id="day6-hike3-6-miles--finished-405pm"&gt;Day6-Hike3, ~6 miles, ???, finished ~4:05pm&lt;/h4&gt;

&lt;p&gt;Not sure how long this took, but my diary entry was at 4:05 so I guess I finished a little before then.&lt;/p&gt;

&lt;h3 id="october-5-day7-friday"&gt;October 5 (Day7), Friday&lt;/h3&gt;

&lt;p&gt;13.02 miles, 1,830 meters lost. Camped at 1890 in hotel.&lt;/p&gt;

&lt;p&gt;2 segments.&lt;/p&gt;

&lt;h4 id="day7-hike1-774-miles-31135-finished-1127am"&gt;&lt;a href="http://rnkpr.com/a219ah5"&gt;Day7-Hike1&lt;/a&gt;, 7.74 miles, 3:11:35, finished 11:27am&lt;/h4&gt;

&lt;p&gt;&lt;img alt="Day7-Hike1, 7.74 miles, 3:11:35, finished 11:27am" src="/assets/images/kilimanjaro_rk_day7_hike1-2023-10-05_0816.png" /&gt;
| &lt;a href="/assets/geo/kilimanjaro_rk_day7_hike1-2023-10-05_0816.gpx"&gt;gpx&lt;/a&gt; | &lt;a href="/assets/geo/kilimanjaro_rk_day7_hike1-2023-10-05_0816.kml"&gt;kml&lt;/a&gt;&lt;/p&gt;

&lt;h4 id="day7-hike2-528-miles-21543-finished-229pm"&gt;&lt;a href="http://rnkpr.com/a219ahl"&gt;Day7-Hike2&lt;/a&gt;, 5.28 miles, 2:15:43, finished 2:29pm&lt;/h4&gt;

&lt;p&gt;&lt;img alt="Day7-Hike2, 5.28 miles, 2:15:43, finished 2:29pm" src="/assets/images/kilimanjaro_rk_day7_hike2-2023-10-05_1204.png" /&gt;
| &lt;a href="/assets/geo/kilimanjaro_rk_day7_hike2-2023-10-05_1204.gpx"&gt;gpx&lt;/a&gt; | &lt;a href="/assets/geo/kilimanjaro_rk_day7_hike2-2023-10-05_1204.kml"&gt;kml&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="summary-table"&gt;Summary Table&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Date&lt;/th&gt;
      &lt;th&gt;Start&lt;/th&gt;
      &lt;th&gt;End&lt;/th&gt;
      &lt;th&gt;Time Walking (hours:minutes)&lt;/th&gt;
      &lt;th&gt;Distance (miles)&lt;/th&gt;
      &lt;th&gt;Altitude Gain (meters)&lt;/th&gt;
      &lt;th&gt;End Altitude (meters)&lt;/th&gt;
      &lt;th&gt;Cumulative Distance (miles)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;9/29&lt;/td&gt;
      &lt;td&gt;1101&lt;/td&gt;
      &lt;td&gt;1618&lt;/td&gt;
      &lt;td&gt;3:28&lt;/td&gt;
      &lt;td&gt;5.54&lt;/td&gt;
      &lt;td&gt;641&lt;/td&gt;
      &lt;td&gt;2,641&lt;/td&gt;
      &lt;td&gt;5.54&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;9/30&lt;/td&gt;
      &lt;td&gt;0837&lt;/td&gt;
      &lt;td&gt;1637&lt;/td&gt;
      &lt;td&gt;5:47&lt;/td&gt;
      &lt;td&gt;6.92&lt;/td&gt;
      &lt;td&gt;1,018&lt;/td&gt;
      &lt;td&gt;3,659&lt;/td&gt;
      &lt;td&gt;12.46&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;10/1&lt;/td&gt;
      &lt;td&gt;0902&lt;/td&gt;
      &lt;td&gt;1152&lt;/td&gt;
      &lt;td&gt;2:50&lt;/td&gt;
      &lt;td&gt;2.20&lt;/td&gt;
      &lt;td&gt;677&lt;/td&gt;
      &lt;td&gt;4,336&lt;/td&gt;
      &lt;td&gt;14.66&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;10/2&lt;/td&gt;
      &lt;td&gt;0906&lt;/td&gt;
      &lt;td&gt;1018&lt;/td&gt;
      &lt;td&gt;1:39&lt;/td&gt;
      &lt;td&gt;1.31&lt;/td&gt;
      &lt;td&gt;164&lt;/td&gt;
      &lt;td&gt;4,500&lt;/td&gt;
      &lt;td&gt;15.97&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;10/3&lt;/td&gt;
      &lt;td&gt;0746&lt;/td&gt;
      &lt;td&gt;1126&lt;/td&gt;
      &lt;td&gt;3:31&lt;/td&gt;
      &lt;td&gt;5.32&lt;/td&gt;
      &lt;td&gt;215&lt;/td&gt;
      &lt;td&gt;4,715&lt;/td&gt;
      &lt;td&gt;21.29&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;10/4 summit&lt;/td&gt;
      &lt;td&gt;2356&lt;/td&gt;
      &lt;td&gt;0712&lt;/td&gt;
      &lt;td&gt;7:16&lt;/td&gt;
      &lt;td&gt;3.50&lt;/td&gt;
      &lt;td&gt;1,195&lt;/td&gt;
      &lt;td&gt;5910&lt;/td&gt;
      &lt;td&gt;24.79&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;10/4 descent&lt;/td&gt;
      &lt;td&gt;0721&lt;/td&gt;
      &lt;td&gt;1605&lt;/td&gt;
      &lt;td&gt;???&lt;/td&gt;
      &lt;td&gt;9.50&lt;/td&gt;
      &lt;td&gt;-995&lt;/td&gt;
      &lt;td&gt;3720&lt;/td&gt;
      &lt;td&gt;34.29&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;10/5&lt;/td&gt;
      &lt;td&gt;0816&lt;/td&gt;
      &lt;td&gt;1429&lt;/td&gt;
      &lt;td&gt;5:27&lt;/td&gt;
      &lt;td&gt;13.02&lt;/td&gt;
      &lt;td&gt;-1,830&lt;/td&gt;
      &lt;td&gt;1,890&lt;/td&gt;
      &lt;td&gt;37.31&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;47.31 miles total, 24.79 miles up, 22.52 down; 6.75 average per day.&lt;/p&gt;

&lt;p&gt;3910 meters of elevation gain; average of 652 across six days climbing.&lt;/p&gt;

&lt;h2 id="advice"&gt;Advice&lt;/h2&gt;

&lt;p&gt;I hope this is helpful. I wanted to know about the specifics of the hikes when I was researching and I wasn’t able to see the kind of pace that people kept.&lt;/p&gt;

&lt;p&gt;What’s interesting to me is that the time actually hiking was rather short each day. Summit day was really long with about 16 hours, but other than that it was usually just a few hours of hiking and the rest of the time hanging around at camp.&lt;/p&gt;

&lt;p&gt;I would bring less equipment next time as I think I probably had 3 or 4 backup layers and fleeces. I just wore the same thing. Maybe two copies of each to alternate them. I definitely wouldn’t bring a soft shell.&lt;/p&gt;

&lt;p&gt;I didn’t bring lightweight but warm camp shoes so I had to wear my boots around camp.&lt;/p&gt;

&lt;p&gt;Altitude was rough so I would try to train more. I live on the east coast and climbed our highest mountains, but that’s only like 5,000 feet. To do it again, I think I’d spend more time in Arusha/Marangu as it’s about 2,000 meters high. And I’d take more time than 7 days. The longer rongai sounds nice or maybe that new northern circuit trek.&lt;/p&gt;

&lt;p&gt;I’m glad that I didn’t skimp on my operator and it was worth everything. The food was nice and the campsite was always great and clean and ready to go when we got there.&lt;/p&gt;

&lt;p&gt;I’d bring more cash to tip. I think I tipped $300 total and that was all the cash I brought. But since our group was smaller than usual with only 3 people, I would have liked to tip more to make up for it.&lt;/p&gt;

&lt;h2 id="procrastination"&gt;Procrastination&lt;/h2&gt;

&lt;p&gt;So I’ve had all this data and all these notes since the trip. I’ve been thinking it over in my head for all these years. I never sat down to write and collect until today. Why did I do that? I’m interested in the trek. I like seeing all the numbers line up. But what’s been pushing this off? I’m not sure. It was hard as I think it took about 8 hours to write everything up and figure out what I wrote in my notes.&lt;/p&gt;

&lt;p&gt;I find it interesting when I know what to do and why, but just don’t do it. I hope to figure out why I do that.&lt;/p&gt;

&lt;h2 id="things-i-read--references"&gt;Things I read / References&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/The_Snows_of_Kilimanjaro_(short_story)"&gt;The Snows of Kilimanjaro by Ernest Hemingway&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.amazon.com/gp/product/1852844132/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&amp;amp;psc=1"&gt;Kilimanjaro: A Trekker’s Guide by Alex Steward (2004, Ciccerone)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.amazon.com/gp/product/1566567815/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&amp;amp;psc=1"&gt;Michael Moushabeck’s Kilimanjaro: A Photographic Journey to the Roof of Africa&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.amazon.com/Kilimanjaro-All-one-Kilimanjaro-Trailblazer/dp/1905864957"&gt;Kilimanjaro - The Trekking Guide to Africa’s Highest Mountain by Henry Stedman&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=FTQbiNvZqaY"&gt;Toto Africa&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.tranquilkilimanjaro.com/places/mawenzi-peak-mount-kilimanjaro/"&gt;Mawenzi Peak&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Mount_Kilimanjaro"&gt;Kilimanjaro&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://peakplanet.com/kilimanjaro-group-dates-prices/"&gt;Peak Planet 2023/2024 Trek Schedule&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>PREPEND</author><pubDate>Sat, 23 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">/hiking/2023/09/quantified-kilimanjaro.html</guid></item><item><title>Inspiration is a Time Machine</title><link>https://www.danstroot.com/posts/2023-09-23-inspiration-is-a-time-machine</link><description>&lt;img alt="post image" src="https://danstroot.imgix.net/assets/blog/img/delorean_bw.jpg" /&gt;&lt;br /&gt;&lt;br /&gt;Companies say they want engaged employees. Funny, I’ve never heard an employee say they wanted to be "engaged" (at least not to their company). Personally, I’d much rather be inspired. When you’re high on inspiration, you can get two weeks of work done in a weekend. Inspiration literally brings the future forward like a time machine.&lt;br /&gt;&lt;br /&gt;This post &lt;a href="https://www.danstroot.com/posts/2023-09-23-inspiration-is-a-time-machine"&gt;Inspiration is a Time Machine&lt;/a&gt; first appeared on &lt;a href="https://www.danstroot.com"&gt;Dan Stroot's Blog&lt;/a&gt;</description><author>Dan Stroot</author><pubDate>Sat, 23 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.danstroot.com/posts/2023-09-23-inspiration-is-a-time-machine</guid></item><item><title>🍂 Ryōkan: In the scenery of spring …</title><link>https://jdsalaro.com/bookmark/poem/ryokan-nothing-better-worse/</link><description>Read more on WikiquoteThis poem by Ryōkan was quoted by Alan Watts1 in one of his lectures2; this was what originally motivated me to look for it in full and reproduce it here. The source of the painting is not related to Ryōkan3.</description><author>Jayson Salazar Rodriguez | @jdsalaro | Blog</author><pubDate>Sat, 23 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jdsalaro.com/bookmark/poem/ryokan-nothing-better-worse/</guid></item><item><title>My Commitment to Intellectual Integrity</title><link>https://lambdaland.org/posts/2023-09-23_all_original/</link><description>&lt;p&gt;I got a strange email the other day. Here it is, with parts redacted:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hi there,&lt;/p&gt;
&lt;p&gt;My name is G—, I am the main editor at —————.&lt;/p&gt;
&lt;p&gt;While browsing your site, I noticed you have an amazing article from this page:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Link to an extremely old post of mine&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;My team actually just published a comprehensive article on “&lt;em&gt;Semi-related title&lt;/em&gt;” which I think your visitors would truly
appreciate and add value to your awesome article.&lt;/p&gt;
&lt;p&gt;You can check it out here: ———————&lt;/p&gt;
&lt;p&gt;If you were willing to add our link to that page, I would be more than happy to share it to more than a thousand of our social followers to help you
gain some visibility in exchange.&lt;/p&gt;
&lt;p&gt;Let me know what you think and thank you for your consideration!&lt;/p&gt;
&lt;p&gt;Cheers,&lt;/p&gt;
&lt;p&gt;G— ———&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;My mail program correctly marked it as spam, which I appreciate. (Besides the spammy feel of the text, the article of mine they reference is an “amazing” &amp;gt;300 word blurb about a one-day project I did in 2017. Sheesh…) I was a little shocked that someone would ask me to engage in the kind of social-metrics-engineering which I feel has contributed directly to many of the social problems plaguing us today.&lt;/p&gt;
&lt;p&gt;I decided to make a public promise that I would hold myself to. Here it is:&lt;/p&gt;
&lt;div class="info"&gt;
&lt;p&gt;I am a scholar. As such, I have a sacred duty to uphold truth at all times and to share knowledge freely. I will never do anything that might compromise my ability to do such. If there is anything beautiful, truthful, or honorable, I will promote it. I will never accept bribery or engage in influence peddling; I find it intellectually and morally repugnant to use my station as an academic for personal gain.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;I’ll make mistakes—and I won’t stop talking about tools and services that I think genuinely serve my desires for greater knowledge, understanding, and freedom—but I will never do this in exchange for money, social clout, or whatever. If I attain any fame, it will not be because it was my end goal.&lt;/p&gt;
&lt;p&gt;So, G—, here is what I think of your offer: &lt;em&gt;get lost—your proposal disgusts me&lt;/em&gt;.&lt;/p&gt;</description><author>Lambda Land</author><pubDate>Sat, 23 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://lambdaland.org/posts/2023-09-23_all_original/</guid></item><item><title>Ukulele chord shapes</title><link>http://localhost:4000/2023/09/23/ukulele-chord-shapes.html</link><description>Ukulele chord shapes</description><author>ms75</author><pubDate>Sat, 23 Sep 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">http://localhost:4000/2023/09/23/ukulele-chord-shapes.html</guid></item><item><title>Wordcount II: Introducing a cleaner C++ class hierarchy to `wc`</title><link>https://bytepawn.com/making-the-wc-state-machines-generic-cpp.html</link><description>&lt;p&gt;In this follow-up to the previous article about writing a C++ version of the Unix command-line utility &lt;code&gt;wc&lt;/code&gt;, I make the class structure more complicated to keep separate concerns and functionality in different C++ classes. The result ends up being significantly more complex than the original, but does not make the overall program easier to understand or modify.&lt;br /&gt;&lt;br /&gt; &lt;img alt="wc" src="/images/wc2.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sat, 23 Sep 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/making-the-wc-state-machines-generic-cpp.html</guid></item><item><title>measure downtime durations via icmp</title><link>https://evilcookie.de/measure-downtime-durations-via-icmp.html</link><description/><author>blog</author><pubDate>Fri, 22 Sep 2023 18:24:32 GMT</pubDate><guid isPermaLink="true">https://evilcookie.de/measure-downtime-durations-via-icmp.html</guid></item><item><title>Suggested readings week 38/2023</title><link>https://www.stefanocosta.org/suggested-readings-week-38-2023/</link><description>&lt;hr /&gt;&lt;h3 id="ai-and-knowledge-workers-a-study"&gt;&lt;a href="https://papers.ssrn.com/sol3/papers.cfm?abstract_id=4573321&amp;amp;ref=stefanocosta.org" rel="nofollow noreferrer noopener"&gt;AI and Knowledge Workers: a study&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;When properly used and managed, Artificial Intelligence in the form of the likes of Generative and Large Language Models enhances knowledge workers productivity and quality, in measureable terms.&lt;/p&gt;&lt;h3 id="figthting-fake-cheese-with-cryptography"&gt;&lt;a href="https://www.theguardian.com/food/2023/aug/18/parmesan-producers-fight-fakes-microtransponders-chips-rind?ref=stefanocosta.org" rel="nofollow noreferrer noopener"&gt;Figthting fake cheese with cryptography&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Parmesan is very expensive: it does make sense to fight fakes&lt;/p&gt;</description><author>Food for Thought (by Stefano Costa)</author><pubDate>Fri, 22 Sep 2023 15:30:03 GMT</pubDate><guid isPermaLink="true">https://www.stefanocosta.org/suggested-readings-week-38-2023/</guid></item><item><title>First you shape your tools...</title><link>https://holzer.online/weeknotes/2023/38/index.html</link><description>&lt;p&gt;
...and then your tools start to shape you.
&lt;/p&gt;

&lt;p&gt;
So, after I've managed &lt;a href="/weeknotes/2023/37/"&gt;last week&lt;/a&gt; to switch over to &lt;a href="https://github.com/fnh/intertwingle/"&gt;my custom SSG&lt;/a&gt; for building the website, this week I spent time to play around and explore a few things, that I would not have done, if I needed to maintain them manually. 
&lt;/p&gt;

&lt;p&gt;
The first addition, inspired by &lt;a href="https://danilafe.com/graph/"&gt;Daniel Fedorin&lt;/a&gt;, is a &lt;a href="/links/internal/graph/"&gt;content graph&lt;/a&gt;, which visualizes how connected my posts actually are. As it turned out, not that much and even less than I had thought. A lot of articles are "orphaned" (meaning, not linked to internally). The second, less pretty, but probably more useful, outcome are automatically created backlinks for each article. The last little new thing is syntax highlighting with &lt;a href="https://prismjs.com/"&gt;Prism&lt;/a&gt;, which yielded the philosophical question: does my css still count as &lt;a href="/articles/benefits-of-classless-css/"&gt;classless&lt;/a&gt;, if the classes in the markup are only created programatically?
&lt;/p&gt;

&lt;p&gt;
To sum up, what started out as the musing of a &lt;a href="/articles/2021-01-20-it-has-been-a-while/"&gt;"weird dude who writes raw HTML"&lt;/a&gt;, is moving up on the x-axis of &lt;a href="https://rakhim.org/honestly-undefined/19/"&gt;this chart&lt;/a&gt; towards the static gen basin. For as, much I would like to pretend otherwise, tools matter at least a little bit.
&lt;/p&gt;

&lt;time datetime="2023-09-22 13:30"&gt;&lt;/time&gt;</description><author>Fabian's public notepad</author><pubDate>Fri, 22 Sep 2023 14:30:00 GMT</pubDate><guid isPermaLink="true">https://holzer.online/weeknotes/2023/38/index.html</guid></item><item><title>Homesteading</title><link>https://alanpearce.eu/post/homesteading/</link><description>Running my own code</description><author>Alan Pearce</author><pubDate>Fri, 22 Sep 2023 13:09:22 GMT</pubDate><guid isPermaLink="true">https://alanpearce.eu/post/homesteading/</guid></item><item><title>A Bit of SaaS Weekly: Build your world</title><link>https://ethanmick.com/a-bit-of-saas-weekly-build-your-world/</link><description>&lt;p&gt;This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by &lt;a href="https://ethanmick.com/"&gt;Ethan Mick&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;The best side project is the one you work on. It&amp;apos;s also the one you use. The perfect place to start building something is to find something you&lt;/p&gt;</description><author>Build SaaS with Ethan</author><pubDate>Fri, 22 Sep 2023 13:00:54 GMT</pubDate><guid isPermaLink="true">https://ethanmick.com/a-bit-of-saas-weekly-build-your-world/</guid></item><item><title>AWS GuardDuty is an Angel</title><link>https://tig.pt/aws-guardduty-is-an-angel/</link><description>&lt;p&gt;A few weeks ago a client asked me: &amp;quot;Hey, can you please create a dev machine on our POC account, we want an internet-facing server with RDP opened on port 3389 for a small test and an RDS Database connected to it, just go simple, ClickOps to test&lt;/p&gt;</description><author>Tiago Rodrigues</author><pubDate>Fri, 22 Sep 2023 00:13:00 GMT</pubDate><guid isPermaLink="true">https://tig.pt/aws-guardduty-is-an-angel/</guid></item><item><title>Java 21 is GA!</title><link>https://backendhance.com/blog/2023/java-21-is-ga/</link><description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;p&gt;Java 21 hat seit Dienstag endlich den General Availability (GA) Status 🎉&lt;/p&gt;
&lt;p&gt;Schon seit Monaten nutze ich jede Gelegenheit, um zu erzählen, dass virtuelle Threads mein Highlight dieses Releases ist. Zum Beispiel
in &lt;a href="https://backendhance.com/blog/2023/nobody-talks-about-project-loom/"&gt;diesem&lt;/a&gt;
oder &lt;a href="https://backendhance.com/blog/2023/i-think-loom-is-going-to-kill-reactive-programming-brian-goetz/"&gt;diesem&lt;/a&gt;
alten Newsletter.&lt;/p&gt;
&lt;p&gt;Und das hat sich auch nicht geändert. Ich glaube virtuelle Threads sind einer der bedeutesten Verbesserungen seit Jahren. Ob das auch alles so eintritt - wird sich zeigen.&lt;/p&gt;
&lt;p&gt;Aber es gibt natürlich auch andere Features, die nicht unerwähnt bleiben sollen.&lt;/p&gt;</description><author>Backendhance</author><pubDate>Thu, 21 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/java-21-is-ga/</guid></item><item><title>K3s Traefik Ingress - configured for your homelab!</title><link>https://sklar.rocks/k3s-traefik-ingress/</link><description>&lt;p&gt;I recently purchased a used Lenovo M900 Think Centre (i7 with 32GB RAM) from eBay to expand my mini-homelab, which was just a single Synology DS218+ plugged into my ISP's router (yuck!). Since I've been spending a big chunk of time at work playing around with Kubernetes, I figured that I'd put my skills to the test and run a &lt;a href="https://k3s.io/" rel="external"&gt;k3s&lt;/a&gt; node on the new server. While I was familiar with k3s before starting this project, I'd never actually run it before, opting for tools like &lt;a href="https://kind.sigs.k8s.io/" rel="external"&gt;kind&lt;/a&gt; (and &lt;a href="https://minikube.sigs.k8s.io/docs/start/" rel="external"&gt;minikube&lt;/a&gt; before that) to run small test clusters for my local development work.&lt;/p&gt;
&lt;p&gt;K3s comes "batteries-included" and is packaged with all of the core components that are needed to run a cluster. For ingress, this component is Traefik, something which I also never had any hands-on experience with.&lt;/p&gt;
&lt;p&gt;After a bit of trial-and-error, along with some help from the docs, I was able to figure out how to configure the Traefik ingress to expose a service on the new node to the rest of my LAN. This post will go into detail about how I did it, in case you've also installed k3s on a node in your own lab and want to know how to use the pre-bundled Traefik ingress.&lt;/p&gt;
&lt;h2 id="installing-k3s"&gt;Installing k3s&lt;/h2&gt;
&lt;p&gt;The k3s installation process is fairly straightforward. It just requires &lt;code&gt;curl&lt;/code&gt;ing &lt;code&gt;https://get.k3s.io&lt;/code&gt; and executing the script. The default settings all make sense to me, except for the need to &lt;code&gt;chmod&lt;/code&gt; the bundled kubeconfig since its default filemode is inaccessible to non-root users. Luckly, k3s has a solution for that! All you need to do is &lt;code&gt;export K3S_KUBECONFIG_MODE="644"&lt;/code&gt; before you run the installation, and the k3s install script does the rest.&lt;/p&gt;
&lt;h2 id="configuring-traefik"&gt;Configuring Traefik&lt;/h2&gt;
&lt;p&gt;By default, host ports 80 and 443 are exposed by the bundled Traefik ingress controller. This will let you create HTTP and HTTPS ingresses on their standard ports.&lt;/p&gt;
&lt;p&gt;But of course, I want to run a &lt;a href="https://questdb.io/" rel="external"&gt;QuestDB&lt;/a&gt; instance on my node, which uses two additional TCP ports for Influx Line Protocol (ILP) and Pgwire communication with the database. So how can I expose these extra ports on my node and route traffic to the QuestDB container running inside of k3s?&lt;/p&gt;
&lt;p&gt;K3s deploys Traefik via a Helm chart and allows you to modify that chart's &lt;code&gt;values.yaml&lt;/code&gt; through a &lt;a href="https://docs.k3s.io/helm#customizing-packaged-components-with-helmchartconfig" rel="external"&gt;HelmChartConfig&lt;/a&gt; resource. To further customize &lt;code&gt;values.yaml&lt;/code&gt; files for installed charts, you can place extra HelmChartConfig manifests in &lt;code&gt;/var/lib/rancher/k3s/server/manifests&lt;/code&gt;. Here is my &lt;code&gt;/var/lib/rancher/k3s/server/manifests/traefik-config.yaml&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="giallo" style="color: #E1E4E8; background-color: #24292E;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #B392F0;"&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;apiVersion&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; helm.cattle.io/v1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;kind&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; HelmChartConfig&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;metadata&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;  name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; traefik&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;  namespace&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; kube-system&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;spec&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;  valuesContent&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #F97583;"&gt; |-&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #9ECBFF;"&gt;    ports:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #9ECBFF;"&gt;      psql:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #9ECBFF;"&gt;        port: 8812&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #9ECBFF;"&gt;        expose: true&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #9ECBFF;"&gt;        exposedPort: 8812&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #9ECBFF;"&gt;        protocol: TCP&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #9ECBFF;"&gt;      ilp:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #9ECBFF;"&gt;        port: 9009&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #9ECBFF;"&gt;        expose: true&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #9ECBFF;"&gt;        exposedPort: 9009&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #9ECBFF;"&gt;        protocol: TCP&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This manifest modifies the Traefik deployment to serve two extra TCP ports on the host: 8812 for Pgwire and 9009 for ILP. Along with these new ports, Traefik also ships with its default "web" and "websecure" ports that route HTTP and HTTPS traffic respectively. But this is already preconfigured for you in the Helm chart, so there's no extra work involved.&lt;/p&gt;
&lt;p&gt;So now that we have our ports exposed, lets figure out how to route traffic from them to our QuestDB instance running inside the cluster.&lt;/p&gt;
&lt;h2 id="ingressroutes"&gt;IngressRoutes&lt;/h2&gt;
&lt;p&gt;While you can use traditional k8s &lt;a href="https://kubernetes.io/docs/concepts/services-networking/ingress/" rel="external"&gt;Ingresses&lt;/a&gt; to configure external access to cluster resources, Traefik v2 also includes new, more flexible types of ingress that coordinate directly with the Traefik deployment. These can be configured by using Traefik-specific Custom Resources, which allow users to specify cluster ingress routes using &lt;a href="https://doc.traefik.io/traefik/routing/routers/" rel="external"&gt;Traefik's custom routing rules&lt;/a&gt; instead of the standard URI-based routing traditionally found in k8s. Using these rules, you can route requests not just based on hostnames and paths, but also by request headers, querystrings, and source IPs, with regex matching support for many of these options. This unlocks significantly more flexibility when routing traffic into your cluster as opposed to using a standard k8s ingress.&lt;/p&gt;
&lt;p&gt;Here's an example of an IngressRoute that I use to expose the QuestDB web console.&lt;/p&gt;
&lt;pre class="giallo" style="color: #E1E4E8; background-color: #24292E;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #B392F0;"&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;kind&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; IngressRoute&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;metadata&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;  name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; questdb&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;  namespace&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; questdb&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;spec&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;  entryPoints&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; web&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;  routes&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span style="color: #85E89D;"&gt; kind&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; Rule&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;      match&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; Host(`my-hostname`)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;      services&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;        -&lt;/span&gt;&lt;span style="color: #85E89D;"&gt; kind&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; Service&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;          name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; questdb&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;          port&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #79B8FF;"&gt; 9000&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This CRD maps the QuestDB service port 9000 to host port 80 through the &lt;code&gt;web&lt;/code&gt; entrypoint, which as I mentioned above, comes pre-installed in the Traefik Helm chart. With this config, I can now access the console by navigating to http://my-hostname/ in my web browser and Traefik will route the request to my QuestDB HTTP service.&lt;/p&gt;
&lt;h3 id="ingressroutetcp"&gt;IngressRouteTCP&lt;/h3&gt;
&lt;p&gt;It's important to note that IngressRoutes are used solely for HTTP ingress routing. Raw TCP routing is done using IngressRouteTCPs, and there are also IngressRouteUDPs available for you to use as well.&lt;/p&gt;
&lt;p&gt;To support the TCP-based ILP and Pgwire protocols, I created 2 &lt;code&gt;IngressRouteTCP&lt;/code&gt; resources to handle traffic on host ports 9009 and 8812.&lt;/p&gt;
&lt;pre class="giallo" style="color: #E1E4E8; background-color: #24292E;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #B392F0;"&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;apiVersion&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; traefik.containo.us/v1alpha1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;kind&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; IngressRouteTCP&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;metadata&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;  name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; questdb-ilp&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;  namespace&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; questdb&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;spec&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;  entryPoints&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; ilp&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;  routes&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span style="color: #85E89D;"&gt; match&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; HostSNI(`*`)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;      services&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;        -&lt;/span&gt;&lt;span style="color: #85E89D;"&gt; name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; questdb&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;          port&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #79B8FF;"&gt; 9009&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;          terminationDelay&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #79B8FF;"&gt; -1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #B392F0;"&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;apiVersion&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; traefik.containo.us/v1alpha1&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;kind&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; IngressRouteTCP&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;metadata&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;  name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; questdb-psql&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;  namespace&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; questdb&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;spec&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;  entryPoints&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; psql&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;  routes&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span style="color: #85E89D;"&gt; match&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; HostSNI(`*`)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;      services&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;        -&lt;/span&gt;&lt;span style="color: #85E89D;"&gt; name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; questdb&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #85E89D;"&gt;          port&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #79B8FF;"&gt; 8812&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, we use the TCP-specific &lt;code&gt;HostSNI&lt;/code&gt; matcher to route all node traffic on ports 9009 (&lt;code&gt;ilp&lt;/code&gt;) and 8812 (&lt;code&gt;psql&lt;/code&gt;) to the questdb service. The &lt;code&gt;ilp&lt;/code&gt; and &lt;code&gt;psql&lt;/code&gt; entrypoints correspond to the &lt;code&gt;ilp&lt;/code&gt; and &lt;code&gt;psql&lt;/code&gt; ports that we exposed in the &lt;code&gt;traefik-config.yaml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now we're able to access QuestDB on all 3 supported ports on my k3s node from the rest of my home network.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I hope this example gives you a bit more confidence when configuring Traefik on a single-node k3s "cluster". It may be a bit confusing at first, but once you have the basics down, you'll be exposing all of your hosted services in no time!&lt;/p&gt;
&lt;p&gt;Since we're only working with one node, there's a limited amount of routing flexibility that I could include in the Traefik CRD configurations. In a more complex environment, you can really go wild with all of the possibilities that are provided by Traefik routing rules! Still, the benefit of this simple example is that you can learn the basics in a small and controlled environment, and then add complexity once it's needed.&lt;/p&gt;
&lt;p&gt;Remember, if you're using k3s clustering mode and running multiple nodes, you'll need to route traffic using an &lt;a href="https://docs.k3s.io/datastore/cluster-loadbalancer" rel="external"&gt;external load balancer&lt;/a&gt; like Haproxy. Maybe I'll play around with this if I ever pick up a second M900 or other mini-desktop. But until then, I'll stick with this ingress configuration.&lt;/p&gt;</description><author>Steven Sklar | My Blog</author><pubDate>Thu, 21 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://sklar.rocks/k3s-traefik-ingress/</guid></item><item><title>NPM download stats are down</title><link>https://francoisbest.com/posts/2023/npm-download-stats-are-down?utm_source=rss</link><description>And people are drawing the funniest conclusions.</description><author>Articles by François Best</author><pubDate>Thu, 21 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://francoisbest.com/posts/2023/npm-download-stats-are-down?utm_source=rss</guid></item><item><title>How do databases execute expressions?</title><link>http://notes.eatonphil.com/2023-09-21-how-do-databases-execute-expressions.html</link><description>&lt;p&gt;Databases are fun. They sit at the confluence of Computer
Science topics that might otherwise not seem practical in life as a
developer. For example, every database with a query language is also a
programming language implementation of some caliber. That doesn't
include all databases though of course; see: RocksDB, FoundationDB,
TigerBeetle, etc.&lt;/p&gt;
&lt;p&gt;This post looks at how various databases execute expressions in their
query language.&lt;/p&gt;
&lt;p&gt;tldr; Most surveyed databases use a tree-walking interpreter. A few
use stack- or register-based virtual machines. A couple have
just-in-time compilers. And, tangentially, a few do vectorized
interpretation.&lt;/p&gt;
&lt;p class="note"&gt;
  Throughout this post I'll use "virtual machine" as a shorthand for
  stack- or register-based loops that process a linearized
  set of instructions. I say this since it is sometimes fair to call a
  tree-walking interpreter a virtual machine. But that is not what I
  mean when I say virtual machine in this post.
&lt;/p&gt;&lt;h3 id="stepping-back"&gt;Stepping back&lt;/h3&gt;&lt;p&gt;Programming languages are typically implemented by turning an
Abstract Syntax Tree (AST) into a linear set of instructions
for a virtual machine (e.g. CPython, Java, C#) or native code
(e.g. GCC's C compiler, Go, Rust). Some of the former implementations
also generate and run Just-In-Time (JIT) compiled native code
(e.g. Java and C#).&lt;/p&gt;
&lt;p&gt;Less commonly these days in programming languages does the
implementation interpret off the AST or some other tree-like
intermediate representation. This style is often called
tree-walking.&lt;/p&gt;
&lt;p&gt;Shell languages sometimes do tree-walking. Otherwise, implementations
that interpret directly off of a tree normally do so as a short-term
measure before switching to compiled virtual machine code or JIT-ed
native code (e.g. some JavaScript implementations, GraalVM, RPython,
etc.)&lt;/p&gt;
&lt;p&gt;That is, while some major programming language implementations started
out with tree-walking interpreters, they mostly moved away from solely
tree-walking over a decade ago. See &lt;a href="https://www.webkit.org/blog/189/announcing-squirrelfish/"&gt;JSC in
2008&lt;/a&gt;, &lt;a href="https://www.infoq.com/news/2007/12/ruby-19/"&gt;Ruby
in 2007&lt;/a&gt;, etc.&lt;/p&gt;
&lt;p&gt;My intuition is that tree-walking takes up more memory and is less
cache-friendly than the linear instructions you give to a virtual
machine or to your CPU. There are &lt;a href="https://stefan-marr.de/downloads/oopsla23-larose-et-al-ast-vs-bytecode-interpreters-in-the-age-of-meta-compilation.pdf"&gt;some folks who
disagree&lt;/a&gt;,
but they mostly talk about tree-walking when you've also got a JIT
compiler hooked up. Which isn't quite the same thing. There has also
been &lt;a href="https://www.cs.cornell.edu/~asampson/blog/flattening.html"&gt;some early exploration and
improvements&lt;/a&gt;
reported when tree-walking with a tree organized as an array.&lt;/p&gt;
&lt;h4 id="and-databases?"&gt;And databases?&lt;/h4&gt;&lt;p&gt;Databases often interpret directly off a tree. (It isn't, generally
speaking, fair to say they are AST-walking interpreters because
databases typically transform and optimize beyond just an AST as
parsed from user code.)&lt;/p&gt;
&lt;p&gt;But not all databases interpret a tree. Some have a virtual
machine. And some generate and run JIT-ed native code.&lt;/p&gt;
&lt;h3 id="methodology"&gt;Methodology&lt;/h3&gt;&lt;p&gt;If a core function (in the query execution path that does something
like arithmetic or comparison) returns a value, that's a sign it's a
tree-walking interpreter. Or, if you see code that is evaluating its
arguments during execution, that's also a sign of a tree-walking
interpreter.&lt;/p&gt;
&lt;p&gt;On the other hand, if the function mutates internal state such as by
assigning a value to a context or pushing to a stack, that's a sign
it's a stack- or register-based virtual machine. If a function pulls
its arguments from memory and doesn't evaluate the arguments, that's
also an indication it's a stack- or register-based virtual machine.&lt;/p&gt;
&lt;p&gt;This approach can result in false-positives depending on the
architecture of the interpreter. User-defined functions (UDFs) would
probably accept evaluated arguments and return a value regardless of
how the interpreter is implemented. So it's important to find not just
functions that could be implemented like UDFs, but core builtin
behavior. Control flow implementations of functions like &lt;code&gt;if&lt;/code&gt; or
&lt;code&gt;case&lt;/code&gt; can be great places to look.&lt;/p&gt;
&lt;p&gt;And tactically, I clone the source code and run stuff like &lt;code&gt;git grep
-i eval | grep -v test | grep \\.java | grep -i eval&lt;/code&gt; or &lt;code&gt;git grep -i
expr | grep -v test | grep \\.go | grep -i expr&lt;/code&gt; until I convince
myself I'm somewhere interesting.&lt;/p&gt;
&lt;p&gt;Note: In talking about a broad swath of projects, maybe I've
misunderstood one or some. If you've got a correction, let me know! If
there's a proprietary database you work on where you can link to the
(publicly described) execution strategy, feel free to pass it along!
Or if I'm missing your public-source database in this list, send me a
message!&lt;/p&gt;
&lt;h3 id="survey"&gt;Survey&lt;/h3&gt;&lt;h4&gt;&lt;a href="https://github.com/cockroachdb/cockroach"&gt;Cockroach&lt;/a&gt; (Ruling: Tree Walker)&lt;/h4&gt;&lt;p&gt;Judging by functions like &lt;a href="https://github.com/cockroachdb/cockroach/blob/master/pkg/sql/sem/eval/expr.go#L105"&gt;&lt;code&gt;func (e *evaluator)
EvalBinaryExpr&lt;/code&gt;&lt;/a&gt;
that &lt;a href="https://github.com/cockroachdb/cockroach/blob/master/pkg/sql/sem/eval/expr.go#L106"&gt;evaluates the left-hand
side&lt;/a&gt;
and then &lt;a href="https://github.com/cockroachdb/cockroach/blob/master/pkg/sql/sem/eval/expr.go#L113"&gt;evaluates the right-hand
side&lt;/a&gt;
and returns a value, Cockroach looks like a tree walking interpreter.&lt;/p&gt;
&lt;p&gt;It gets a little more interesting though, since Cockroach also
&lt;a href="https://www.cockroachlabs.com/docs/stable/vectorized-execution"&gt;supports&lt;/a&gt;
vectorized expression execution. Vectorizing is a fancy term for
acting on many pieces of data at once rather than one at a time. It
doesn't necessarily imply SIMD. Here is an example of a &lt;a href="https://github.com/cockroachdb/cockroach/blob/master/pkg/sql/colexec/colexecproj/proj_non_const_ops.eg.go#L4427"&gt;vectorized
addition&lt;/a&gt;
of two int64 columns.&lt;/p&gt;
&lt;h4&gt;&lt;a href="https://github.com/ClickHouse/clickhouse"&gt;ClickHouse&lt;/a&gt; (Ruling: Tree Walker + JIT)&lt;/h4&gt;&lt;p&gt;The ClickHouse architecture is a little unique and difficult for me to
read through – likely due to it being fairly mature, with serious
optimization. But they tend to document their header files well. So
files like
&lt;a href="https://github.com/ClickHouse/ClickHouse/blob/853e3f0aa789d5b6dcb251a403276d9fdc02902c/src/Functions/IFunction.h"&gt;src/Functions/IFunction.h&lt;/a&gt;
and
&lt;a href="https://github.com/ClickHouse/ClickHouse/blob/9af9b4a08542812694f171833a7afe08f5aaaafb/src/Interpreters/ExpressionActions.h"&gt;src/Interpreters/ExpressionActions.h&lt;/a&gt;
were helpful.&lt;/p&gt;
&lt;p&gt;They have also spoken publicly about their pipeline execution model;
e.g. &lt;a href="https://presentations.clickhouse.com/meetup24/5.%20Clickhouse%20query%20execution%20pipeline%20changes/"&gt;this
presentation&lt;/a&gt;
and &lt;a href="https://github.com/ClickHouse/ClickHouse/issues/34045"&gt;this roadmap
issue&lt;/a&gt;. But it
isn't completely clear how much pipeline execution (which is broader
than just expression evaluation) connects to expression evaluation.&lt;/p&gt;
&lt;p&gt;Moreover, they have &lt;a href="https://clickhouse.com/blog/clickhouse-just-in-time-compiler-jit"&gt;publicly
spoken&lt;/a&gt;
about their support for JIT compilation for query execution. But let's
look at how execution works when the JIT is not enabled. For example,
If we take a look at how &lt;a href="https://github.com/ClickHouse/ClickHouse/blob/853e3f0aa789d5b6dcb251a403276d9fdc02902c/src/Functions/if.cpp"&gt;&lt;code&gt;if&lt;/code&gt; is
implemented&lt;/a&gt;,
we know that the &lt;code&gt;then&lt;/code&gt; and &lt;code&gt;else&lt;/code&gt; rows must be conditionally
evaluated.&lt;/p&gt;
&lt;p&gt;In the runtime entrypoint,
&lt;a href="https://github.com/ClickHouse/ClickHouse/blob/853e3f0aa789d5b6dcb251a403276d9fdc02902c/src/Functions/if.cpp#L1048"&gt;&lt;code&gt;executeImpl&lt;/code&gt;&lt;/a&gt;,
we see the function call
&lt;a href="https://github.com/ClickHouse/ClickHouse/blob/853e3f0aa789d5b6dcb251a403276d9fdc02902c/src/Functions/if.cpp#L983"&gt;&lt;code&gt;executeShortCircuitArguments&lt;/code&gt;&lt;/a&gt;
which in turn calls
&lt;a href="https://github.com/ClickHouse/ClickHouse/blob/master/src/Columns/ColumnFunction.cpp#L280"&gt;&lt;code&gt;ColumnFunction::reduce()&lt;/code&gt;&lt;/a&gt;
which &lt;a href="https://github.com/ClickHouse/ClickHouse/blob/master/src/Columns/ColumnFunction.cpp#L299"&gt;evaluates each column vector that is an
argument&lt;/a&gt;
to the function and then calls execute on the function.&lt;/p&gt;
&lt;p&gt;So from this we can tell the non-JIT execution is a tree walker and
that it is over &lt;a href="https://twitter.com/ClickHouseDB/status/1705619463888900538"&gt;chunks of
columns&lt;/a&gt;,
i.e. vectorized data, similar to Cockroach. However in ClickHouse
execution is &lt;em&gt;always&lt;/em&gt; over column vectors.&lt;/p&gt;
&lt;p class="note"&gt;
  In the original version of this post, I had some confusion about the
  ClickHouse execution strategy. Robert Schulze from
  ClickHouse &lt;a href="https://clickhousedb.slack.com/archives/CUDSPUJ68/p1695307656700889"&gt;helped
  clarify&lt;/a&gt; things for me. Thanks Robert!
&lt;/p&gt;&lt;h4&gt;&lt;a href="https://github.com/duckdb/duckdb"&gt;DuckDB&lt;/a&gt; (Ruling: Tree Walker)&lt;/h4&gt;&lt;p&gt;If we take a look at how &lt;a href="https://github.com/duckdb/duckdb/blob/479c89e154f32012143d741c1a4f4d769f20044e/src/execution/expression_executor/execute_function.cpp#L59"&gt;function expressions are
executed&lt;/a&gt;,
we can see each &lt;a href="https://github.com/duckdb/duckdb/blob/479c89e154f32012143d741c1a4f4d769f20044e/src/execution/expression_executor/execute_function.cpp#L66"&gt;argument in the function being
evaluated&lt;/a&gt;
before being passed to the actual function. So that looks like a tree
walking interpreter.&lt;/p&gt;
&lt;p&gt;Like ClickHouse, DuckDB expression execution is always over column
vectors. You can read more about this architecture
&lt;a href="https://duckdb.org/internals/vector.html"&gt;here&lt;/a&gt; and
&lt;a href="https://www.infoq.com/articles/analytical-data-management-duckdb/"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;&lt;a href="https://github.com/influxdata/influxdb"&gt;Influx&lt;/a&gt; (Ruling: Tree Walker)&lt;/h4&gt;&lt;p&gt;Influx originally had a SQL-like query language called InfluxQL. If we
look at &lt;a href="https://github.com/influxdata/influxdb/blob/b3b982d746fdc34451ca44d262f83b483cd9ea33/storage/reads/influxql_eval.go#L41"&gt;how it evaluates a binary
expression&lt;/a&gt;,
it first evaluates the left-hand side and then the right-hand side
before operating on the sides and returning a value. That's a
tree-walking interpreter.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/influxdata/flux"&gt;Flux&lt;/a&gt; was the new query language
for Influx. While the Flux
&lt;a href="https://github.com/influxdata/flux/blob/master/docs/VirtualMachine.md"&gt;docs&lt;/a&gt;
suggest they transform to an intermediate representation that is
executed on a virtual machine, there's nothing I'm seeing that looks
like a stack- or register-based virtual machine. All the &lt;a href="https://github.com/influxdata/flux/blob/master/interpreter/interpreter.go#L352"&gt;evaluation
functions&lt;/a&gt;
evaluate their arguments and return a value. That looks like a
tree-walking interpreter to me.&lt;/p&gt;
&lt;p&gt;Today Influx
&lt;a href="https://www.influxdata.com/blog/the-plan-for-influxdb-3-0-open-source/"&gt;announced&lt;/a&gt;
that Flux is in maintenance mode and they are focusing on InfluxQL
again.&lt;/p&gt;
&lt;h4&gt;&lt;a href="https://github.com/MariaDB/server"&gt;MariaDB&lt;/a&gt; / &lt;a href="https://github.com/mysql/mysql-server"&gt;MySQL&lt;/a&gt; (Ruling: Tree Walker)&lt;/h4&gt;&lt;p&gt;Control flow methods are normally a good way to see how an interpreter
is implemented. The implementation of COALESCE &lt;a href="https://github.com/MariaDB/server/blob/e9573c059656d9477c2176f102f7e79d0f1ca6b0/sql/item_cmpfunc.cc#L3431"&gt;looks pretty
simple&lt;/a&gt;. We
see it &lt;a href="https://github.com/MariaDB/server/blob/e9573c059656d9477c2176f102f7e79d0f1ca6b0/sql/item_cmpfunc.cc#L3442"&gt;call
&lt;code&gt;val_str()&lt;/code&gt;&lt;/a&gt;
for each argument to COALESCE. But I can only seem to find
implementations of &lt;code&gt;val_str()&lt;/code&gt; on raw values and not
expressions. &lt;code&gt;Item_func_coalesce&lt;/code&gt; itself does not implement
&lt;code&gt;val_str()&lt;/code&gt; for example, which would be a strong indication of a tree
walker. Maybe it does implement &lt;code&gt;val_str()&lt;/code&gt; through inheritance.&lt;/p&gt;
&lt;p&gt;It becomes a little clearer if we look at non-control flow methods
like
&lt;a href="https://github.com/MariaDB/server/blob/e9573c059656d9477c2176f102f7e79d0f1ca6b0/sql/item_func.cc#L2048"&gt;&lt;code&gt;acos&lt;/code&gt;&lt;/a&gt;. In
this method we see &lt;code&gt;Item_func_acos&lt;/code&gt; itself implement &lt;code&gt;val_real()&lt;/code&gt; and
also call &lt;code&gt;val_real()&lt;/code&gt; on all its arguments. In this case it's obvious
how the control flow of &lt;code&gt;acos(acos(.5))&lt;/code&gt; would work. So that seems to
indicate expressions are executed with a tree walking interpreter.&lt;/p&gt;
&lt;p&gt;I also noticed
&lt;a href="https://github.com/MariaDB/server/blob/e9573c059656d9477c2176f102f7e79d0f1ca6b0/sql/sp_instr.cc"&gt;sql/sp_instr.cc&lt;/a&gt;. That
is scary (in terms of invalidating my analysis) since it looks like a
virtual machine. But after looking through it, I think this virtual
machine only corresponds to how stored procedures are executed, hence
the &lt;code&gt;sp_&lt;/code&gt; prefix for Stored Programs. &lt;a href="https://dev.mysql.com/doc/dev/mysql-server/latest/stored_programs.html"&gt;MySQL
docs&lt;/a&gt;
also explain that stored procedures are executed with a bytecode
virtual machine.&lt;/p&gt;
&lt;p&gt;I'm curious why they don't use that virtual machine for query
execution.&lt;/p&gt;
&lt;p&gt;As far as I can tell MySQL and MariaDB do not differ in this regard.&lt;/p&gt;
&lt;h4&gt;&lt;a href="https://github.com/mongodb/mongo"&gt;MongoDB&lt;/a&gt; (Ruling: Virtual Machine)&lt;/h4&gt;&lt;p&gt;Mongo &lt;a href="https://laplab.me/posts/inside-new-query-engine-of-mongodb/"&gt;recently
introduced&lt;/a&gt;
a virtual machine for executing queries, called Slot Based Execution
(SBE). We can find the SBE code in
&lt;a href="https://github.com/mongodb/mongo/blob/master/src/mongo/db/exec/sbe/vm/vm.cpp#L9313"&gt;src/mongo/db/exec/sbe/vm/vm.cpp&lt;/a&gt;
and the main virtual machine entrypoint
&lt;a href="https://github.com/mongodb/mongo/blob/master/src/mongo/db/exec/sbe/vm/vm.cpp#L9313"&gt;here&lt;/a&gt;. &lt;a href="https://github.com/mongodb/mongo/blob/master/src/mongo/db/exec/sbe/vm/vm.cpp#L9419"&gt;Looks
like&lt;/a&gt;
a classic stack-based virtual machine!&lt;/p&gt;
&lt;p&gt;It isn't completely clear to me if the SBE path is always used or if
there are still cases where it falls back to their old execution
model. You can read more about Mongo execution
&lt;a href="https://github.com/mongodb/mongo/blob/master/src/mongo/db/query/README.md"&gt;here&lt;/a&gt;
and &lt;a href="https://www.mongodb.com/docs/manual/reference/sbe/"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;&lt;a&gt;PostgreSQL&lt;/a&gt; (Ruling: Virtual Machine + JIT)&lt;/h4&gt;&lt;p&gt;The top of PostgreSQL's
&lt;a href="https://github.com/postgres/postgres/blob/cca97ce6a6653df7f4ec71ecd54944cc9a6c4c16/src/backend/executor/execExprInterp.c#L6"&gt;src/backend/executor/execExprInterp.c&lt;/a&gt;
clearly explains that expression execution uses a virtual machine. You
see all the hallmarks: opcodes, a loop over a giant switch, etc. And
if we look at how &lt;a href="https://github.com/postgres/postgres/blob/cca97ce6a6653df7f4ec71ecd54944cc9a6c4c16/src/backend/executor/execExprInterp.c#L728"&gt;function expressions are
executed&lt;/a&gt;,
we see another hallmark which is that the function expression code
doesn't evaluate its arguments. They've already been evaluated. And
function expression code just acts on the results of its arguments.&lt;/p&gt;
&lt;p&gt;PostgreSQL also
&lt;a href="https://github.com/postgres/postgres/blob/master/src/backend/jit/README"&gt;supports&lt;/a&gt;
JIT-ing expression execution. And we can find the switch between
interpreting and JIT-compiling an expression
&lt;a href="https://github.com/postgres/postgres/blob/cca97ce6a6653df7f4ec71ecd54944cc9a6c4c16/src/backend/executor/execExpr.c#L873"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;&lt;a href="https://github.com/questdb/questdb"&gt;QuestDB&lt;/a&gt; (Ruling: Tree Walker + JIT)&lt;/h4&gt;&lt;p&gt;QuestDB &lt;a href="https://questdb.io/blog/2022/01/12/jit-sql-compiler/"&gt;wrote about their execution engine
recently&lt;/a&gt;. When
the conditions are right, they'll &lt;a href="https://github.com/questdb/questdb/blob/11ac85510292596f0d21b10603e500f8edb5e486/core/src/main/java/io/questdb/griffin/SqlCodeGenerator.java#L1394"&gt;switch over to a JIT
compiler&lt;/a&gt;
and run native code.&lt;/p&gt;
&lt;p&gt;But let's look at the default path. For example, how &lt;a href="https://github.com/questdb/questdb/blob/11ac85510292596f0d21b10603e500f8edb5e486/core/src/main/java/io/questdb/griffin/engine/functions/bool/AndFunctionFactory.java#L82"&gt;&lt;code&gt;AND&lt;/code&gt; is
implemented&lt;/a&gt;. &lt;code&gt;AndBooleanFunction&lt;/code&gt;
implements &lt;code&gt;BooleanFunction&lt;/code&gt; which implements &lt;code&gt;Function&lt;/code&gt;. An
expression can be evaluated by calling a &lt;code&gt;getX()&lt;/code&gt; method on the
expression type that implements &lt;code&gt;Function&lt;/code&gt;. &lt;code&gt;AndBooleanFunction&lt;/code&gt; calls
&lt;code&gt;getBool()&lt;/code&gt; on its left and right hand sides. And if we look at the
&lt;a href="https://github.com/questdb/questdb/blob/11ac85510292596f0d21b10603e500f8edb5e486/core/src/main/java/io/questdb/griffin/engine/functions/BooleanFunction.java#L35"&gt;partial
implementation&lt;/a&gt;
of &lt;code&gt;BooleanFunction&lt;/code&gt; we'll also see it doing &lt;code&gt;getX()&lt;/code&gt; specific
conversions during the call of &lt;code&gt;getX()&lt;/code&gt;. So that's a tree-walking
interpreter.&lt;/p&gt;
&lt;h4&gt;&lt;a href="https://github.com/scylladb/scylladb"&gt;Scylla&lt;/a&gt; (Ruling: Tree Walker)&lt;/h4&gt;&lt;p&gt;If we take a look at how &lt;a href="https://github.com/scylladb/scylladb/blob/08197882074227edbd0a95f49914913e3124753d/cql3/expr/expression.cc#L2145"&gt;functions are
evaluated&lt;/a&gt;
in Scylla, we see function evaluation first &lt;a href="https://github.com/scylladb/scylladb/blob/08197882074227edbd0a95f49914913e3124753d/cql3/expr/expression.cc#L2161"&gt;evaluating all of its
arguments&lt;/a&gt;. And
the function evaluation function itself returns a
&lt;code&gt;cql3::raw_value&lt;/code&gt;. So that's a tree-walking interpreter.&lt;/p&gt;
&lt;h4&gt;&lt;a href="https://github.com/sqlite/sqlite"&gt;SQLite&lt;/a&gt; (Ruling: Virtual Machine)&lt;/h4&gt;&lt;p&gt;SQLite's virtual machine is &lt;a href="https://www.sqlite.org/opcode.html"&gt;comprehensive and
well-documented&lt;/a&gt;. It encompasses
more than just expression evaluation but the entirety of query
execution.&lt;/p&gt;
&lt;p&gt;We can find the massive virtual machine switch in
&lt;a href="https://github.com/sqlite/sqlite/blob/8aaf63c6ac8b8292c0ecead0d2b04b68e9e6be78/src/vdbe.c#L971"&gt;src/vdbe.c&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And if we look, for example, at how &lt;code&gt;AND&lt;/code&gt; is implemented, we see it
&lt;a href="https://github.com/sqlite/sqlite/blob/8aaf63c6ac8b8292c0ecead0d2b04b68e9e6be78/src/vdbe.c#L2536"&gt;pulling its arguments out of
memory&lt;/a&gt;
(already evaluated) and assigning the result back to &lt;a href="https://github.com/sqlite/sqlite/blob/8aaf63c6ac8b8292c0ecead0d2b04b68e9e6be78/src/vdbe.c#L2545"&gt;a designated
point in
memory&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;SingleStore (Ruling: Virtual Machine + JIT)&lt;/h4&gt;&lt;p&gt;While there's no source code to link to, SingleStore &lt;a href="https://www.youtube.com/watch?v=_vloWsdPCDs&amp;amp;t=3810s"&gt;gave a talk at
CMU&lt;/a&gt; that broke
down their query execution pipeline. Their
&lt;a href="https://docs.singlestore.com/cloud/query-data/advanced-query-topics/code-generation/"&gt;docs&lt;/a&gt;
also cover the topic.&lt;/p&gt;
&lt;p&gt;&lt;img alt="SingleStore compiler pipeline" src="/assets/memsql.webp" /&gt;&lt;/p&gt;
&lt;h4&gt;&lt;a href="https://github.com/pingcap/tidb"&gt;TiDB&lt;/a&gt; (Ruling: Tree Walker)&lt;/h4&gt;&lt;p&gt;Similar to DuckDB and ClickHouse, TiDB implements vectorized
interpretation. They've &lt;a href="https://www.pingcap.com/blog/10x-performance-improvement-for-expression-evaluation-made-possible-by-vectorized-execution/"&gt;written publicly about their switch to this
method&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let's take a look at how &lt;code&gt;if&lt;/code&gt; is implemented in TiDB. There is a
vectorized and non-vectorized version of &lt;code&gt;if&lt;/code&gt; (in
&lt;a href="https://github.com/pingcap/tidb/blob/3ccd09e63addddeb0d33b5b87594a2d61fffd1d8/expression/builtin_control.go"&gt;expression/control_builtin.go&lt;/a&gt;
and
&lt;a href="https://github.com/pingcap/tidb/blob/3ccd09e63addddeb0d33b5b87594a2d61fffd1d8/expression/builtin_control_vec_generated.go"&gt;expression/control_builtin_generated.go&lt;/a&gt;
respectively). So maybe they haven't completely switched over to
vectorized execution or maybe it can only be used in some conditions.&lt;/p&gt;
&lt;p&gt;If we look at the &lt;a href="https://github.com/pingcap/tidb/blob/3ccd09e63addddeb0d33b5b87594a2d61fffd1d8/expression/builtin_control.go#L599"&gt;non-vectorized version of
&lt;code&gt;if&lt;/code&gt;&lt;/a&gt;,
we see the &lt;a href="https://github.com/pingcap/tidb/blob/3ccd09e63addddeb0d33b5b87594a2d61fffd1d8/expression/builtin_control.go#L600"&gt;condition
evaluated&lt;/a&gt;. And
then the &lt;code&gt;then&lt;/code&gt; or &lt;code&gt;else&lt;/code&gt; is evaluated &lt;a href="https://github.com/pingcap/tidb/blob/3ccd09e63addddeb0d33b5b87594a2d61fffd1d8/expression/builtin_control.go#L604"&gt;depending on the result of the
condition&lt;/a&gt;. That's
a tree-walking interpreter.&lt;/p&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;&lt;p&gt;As the DuckDB team &lt;a href="https://duckdb.org/why_duckdb.html"&gt;points out&lt;/a&gt;,
vectorized interpretation or JIT compilation &lt;a href="https://www.vldb.org/pvldb/vol11/p2209-kersten.pdf"&gt;seem like the
future&lt;/a&gt; for
database expression execution. These strategies seem particularly
important for analytics or time-series workloads. But vectorized
interpretation seems to make the most sense for column-wise storage
engines. And column-wise storage normally only makes sense for
analytics workloads. Still, TiDB and Cockroach are transactional
databases that also vectorize execution.&lt;/p&gt;
&lt;p&gt;And while SQLite and PostgreSQL use the virtual machine model, it's
possible databases with tree-walking interpreters like Scylla and
MySQL/MariaDB have decided there is not significant enough gains to be
had (for transactional workloads) to justify the complexity of moving
to a compiler + virtual machine architecture.&lt;/p&gt;
&lt;p&gt;Tree-walking interpreters and virtual machines are also independent
from whether or not execution is vectorized. So that will be another
interesting dimension to watch: if more databases move toward
vectorized execution even if they don't adapt JIT compilation.&lt;/p&gt;
&lt;p&gt;Yet another alternative is that maybe as databases mature we'll see
compilation tiers similar to what &lt;a href="https://webkit.org/blog/9329/a-new-bytecode-format-for-javascriptcore/"&gt;browsers
do&lt;/a&gt;
&lt;a href="https://v8.dev/blog/sparkplug"&gt;with JavaScript&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Credits: Thanks Max Bernstein, Alex Miller, and Justin Jaffray for
reviewing a draft version of this! And thanks to the #dbs channel on
&lt;a href="https://eatonphil.com/discord.html"&gt;Discord&lt;/a&gt; for instigating this
post!&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I spent some time looking into how various databases execute expressions in their query language.&lt;br /&gt;&lt;br /&gt;Most of them have a tree-walking interpreter, some have a virtual machine, and some do just-in-time compilation.&lt;br /&gt;&lt;br /&gt;Let's dig into some database code to see!&lt;a href="https://t.co/BIGtHKh1X4"&gt;https://t.co/BIGtHKh1X4&lt;/a&gt; &lt;a href="https://t.co/nmhe9HmYw7"&gt;pic.twitter.com/nmhe9HmYw7&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1704936432412868725?ref_src=twsrc%5Etfw"&gt;September 21, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Thu, 21 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2023-09-21-how-do-databases-execute-expressions.html</guid></item><item><title>Getting the Absolute Path of a Remote Directory in Ansible</title><link>https://tiim.ch/blog/2023-09-20-ansible-absolute-path</link><description>There is no builtin way to convert a relative path to an absolute path in ansible. However we can use the readlink command for this.</description><author>Tim Bachmann's Blog</author><pubDate>Thu, 21 Sep 2023 00:39:13 GMT</pubDate><guid isPermaLink="true">https://tiim.ch/blog/2023-09-20-ansible-absolute-path</guid></item><item><title>How to load a local Parquet file in Starrocks</title><link>https://tommasoamici.com/blog/how-to-load-a-local-parquet-file-in-starrocks/</link><description>A short guide on how to ingest data in Starrocks using Parquet files&lt;hr /&gt;&lt;a href="https://tommasoamici.com/blog/how-to-load-a-local-parquet-file-in-starrocks/"&gt;Read more&lt;/a&gt;</description><author>Tommaso Amici</author><pubDate>Wed, 20 Sep 2023 23:37:33 GMT</pubDate><guid isPermaLink="true">https://tommasoamici.com/blog/how-to-load-a-local-parquet-file-in-starrocks/</guid></item><item><title>You may not need Nx Cloud</title><link>https://tommasoamici.com/blog/you-may-not-need-nx-cloud/</link><description>Some of the things I learned while migrating the frontend monorepo at FLYR from Lerna to Nx and how to make the most out of this setup without Nx Cloud.&lt;hr /&gt;&lt;a href="https://tommasoamici.com/blog/you-may-not-need-nx-cloud/"&gt;Read more&lt;/a&gt;</description><author>Tommaso Amici</author><pubDate>Wed, 20 Sep 2023 22:35:36 GMT</pubDate><guid isPermaLink="true">https://tommasoamici.com/blog/you-may-not-need-nx-cloud/</guid></item><item><title>How to set up Google OAuth with Next.js using Next-Auth</title><link>https://ethanmick.com/how-to-set-up-google-oauth-with-next-js-using-next-auth/</link><description>&lt;figure class="kg-card kg-embed-card"&gt;&lt;/figure&gt;&lt;p&gt;This guide runs through the full setup of OAuth with Google as the identity provider. This will let your users log in with Google.&lt;/p&gt;&lt;h2 id="google-cloud-console"&gt;Google Cloud Console&lt;/h2&gt;&lt;p&gt;The best way to get through the console is to watch the video above.&lt;/p&gt;&lt;figure class="kg-card kg-bookmark-card"&gt;&lt;a class="kg-bookmark-container" href="https://console.cloud.google.com/?ref=ethanmick.com"&gt;&lt;div class="kg-bookmark-content"&gt;&lt;div class="kg-bookmark-title"&gt;Google Cloud Platform&lt;/div&gt;&lt;small&gt;&lt;div class="kg-bookmark-description"&gt;Google Cloud Platform lets you build,&lt;/div&gt;&lt;/small&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;</description><author>Build SaaS with Ethan</author><pubDate>Wed, 20 Sep 2023 21:23:14 GMT</pubDate><guid isPermaLink="true">https://ethanmick.com/how-to-set-up-google-oauth-with-next-js-using-next-auth/</guid></item><item><title>Something I'm Doing With ChatGPT</title><link>https://matteoraso.github.io/something-im-doing-with-chatgpt.html</link><description>&lt;p&gt;I literally just finished playing around with ChatGPT, and I found that it's really good at making stories, especially in other languages. Since I'm trying to learn Italian, I made it give me some simple Italian stories to practice off of. Before, I had to use complicated news articles, so …&lt;/p&gt;</description><author>Matteo's Website</author><pubDate>Wed, 20 Sep 2023 07:00:00 GMT</pubDate><guid isPermaLink="true">https://matteoraso.github.io/something-im-doing-with-chatgpt.html</guid></item><item><title>Making it easier to maintain open-source projects with CodiumAI and Pinecone</title><link>https://zackproser.com/blog/codium-ai-pinecone-pr-agent</link><description>CodiumAI's PR-agent integration leverages Pinecone under the hood to perform semantic search for similar GitHub issues</description><author>Zachary Proser</author><pubDate>Wed, 20 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/codium-ai-pinecone-pr-agent</guid></item><item><title>Nevítaný host</title><link>https://vit.baisa.cz/books/nevitany-host/</link><description>detektivní příběh z horského hotýlku</description><author>Vít Baisa</author><pubDate>Wed, 20 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://vit.baisa.cz/books/nevitany-host/</guid></item><item><title>Storing React state in the URL with Next.js</title><link>https://francoisbest.com/posts/2023/storing-react-state-in-the-url-with-nextjs?utm_source=rss</link><description>A peek under the hood of the next-usequerystate 1.8.x update with support for the app router.</description><author>Articles by François Best</author><pubDate>Wed, 20 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://francoisbest.com/posts/2023/storing-react-state-in-the-url-with-nextjs?utm_source=rss</guid></item><item><title>Attributes of a Pc</title><link>https://one.mikro2nd.net/pages/attributes-of-a-pc/</link><description>coming soon&amp;hellip;</description><author>one mikro2nd</author><pubDate>Wed, 20 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://one.mikro2nd.net/pages/attributes-of-a-pc/</guid></item><item><title>Naming Variables Just Got Harder</title><link>https://blog.randombits.host/naming-variables-just-got-harder/</link><description>&lt;p&gt;&lt;a href="https://www.martinfowler.com/bliki/TwoHardThings.html?ref=blog.randombits.host"&gt;It&amp;apos;s a joke that has been done to death&lt;/a&gt;, but it is true. Naming things is one of the hardest aspects of Computer Science. It affects the readability, maintainability, and every facet of the lossy interface between the concept in your mind, and the cold reality of the&lt;/p&gt;</description><author>Random Bits</author><pubDate>Wed, 20 Sep 2023 00:43:25 GMT</pubDate><guid isPermaLink="true">https://blog.randombits.host/naming-variables-just-got-harder/</guid></item><item><title>Announcing Daylight Goals 1.0 🎉</title><link>https://swiftfox.co/2023/09/announcing-daylight-goals-1-0-%f0%9f%8e%89/</link><description>&lt;p&gt;For the first time in a while, I have released a completely new product! This is an Apple Watch focused app, that works with the new Time In Daylight tracking features of watchOS 10. Using Daylight Goals you can easily setup a goal for how much time you want to spend outside each day. It...  &lt;a class="excerpt-read-more" href="https://swiftfox.co/2023/09/announcing-daylight-goals-1-0-%f0%9f%8e%89/" title="ReadAnnouncing Daylight Goals 1.0 🎉"&gt;Read more &amp;#187;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://swiftfox.co/2023/09/announcing-daylight-goals-1-0-%f0%9f%8e%89/"&gt;Announcing Daylight Goals 1.0 🎉&lt;/a&gt; first appeared on &lt;a href="https://swiftfox.co"&gt;Swift Fox Software LLC&lt;/a&gt;.&lt;/p&gt;</description><author>Swift Fox Software LLC</author><pubDate>Tue, 19 Sep 2023 16:03:48 GMT</pubDate><guid isPermaLink="true">https://swiftfox.co/2023/09/announcing-daylight-goals-1-0-%f0%9f%8e%89/</guid></item><item><title>git.voussoir.net</title><link>https://voussoir.net/writing/git_dot_voussoir_dot_net</link><description>&lt;article&gt;
&lt;p&gt;&lt;a href="/writing"&gt;Back to writing&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;git.voussoir.net&lt;/h1&gt;
&lt;p&gt;Github has been bothering me. I don't like their &lt;a href="/writing/git_dot_voussoir_dot_net/lets_build_from_here.html"&gt;drunk-on-buzzwords marketing copy&lt;/a&gt; or the &lt;a href="/writing/git_dot_voussoir_dot_net/copilot.png"&gt;advertisements they're placing above my work&lt;/a&gt;. If I give someone a link to my work, I want to feel proud of it.&lt;/p&gt;
&lt;p&gt;So, I installed &lt;a href="https://gitea.com"&gt;Gitea&lt;/a&gt; on my server and uploaded everything to &lt;a href="https://git.voussoir.net"&gt;git.voussoir.net&lt;/a&gt;. This was easy because I have no investment or dependency on github's issue tracker, project tracker, wiki, pages, CI, Actions, or whatever other proprietary lock-ins they offer. I will need to move some image assets eventually and that's about it.&lt;/p&gt;
&lt;p&gt;I had tried &lt;a href="https://gogs.io/"&gt;Gogs&lt;/a&gt; at first, but it was failing on every push with a &lt;a href="/writing/git_dot_voussoir_dot_net/gogs_502.png"&gt;502 error&lt;/a&gt; that I couldn't diagnose.&lt;/p&gt;
&lt;p&gt;I followed Gitea's &lt;a href="https://docs.gitea.com/installation/install-from-binary"&gt;installation from binary&lt;/a&gt; instructions and updated my nginx settings. From start to finish, the process took maybe an hour and a half, which was a little longer than it should have been because gitea was trying to do some mkdir in &lt;code&gt;/usr/local/bin/data/tmp&lt;/code&gt; even though I asked it to use a different path. I had to manually set &lt;code&gt;APP_DATA_PATH&lt;/code&gt; in the &lt;code&gt;.ini&lt;/code&gt; file to make it stop doing that, and now it's fine. Even if I accidentally explode the whole server somehow, I can just reinstall gitea from scratch and push it back again. I &lt;em&gt;still&lt;/em&gt; don't plan to use the issue tracker or whatever CI they come up with. I am deliberately calling it git.voussoir.net instead of gitea.voussoir.net in case I change the backing software.&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;Just a week after setting up Gitea, I learned that Gitea made a controversial move last year in forming a for-profit entity, and a new non-profit fork called &lt;a href="https://forgejo.org/faq/#why-was-forgejo-created"&gt;Forgejo&lt;/a&gt; has started. I haven't personally being slighted. I just got here. But the words "non-profit" are like catnip to me so it looks like git.voussoir.net runs Forgejo now. Wow, my foresight paid off!&lt;/p&gt;
&lt;p&gt;I won't delete my github account in the foreseeable future, because it's free and easy to keep pushing to all remotes. But github is losing my favor, and that's great for them too because I'm a nobody and I wasn't paying them anyway. So long, and thanks for all the fetch.&lt;/p&gt;
&lt;p&gt;Note: If you want to keep it even simpler than this, git does have a web UI built in. See &lt;a href="https://git-scm.com/book/en/v2/Git-on-the-Server-GitWeb"&gt;gitweb&lt;/a&gt;.&lt;/p&gt;

&lt;hr /&gt;
&lt;p&gt;&lt;a href="https://git.voussoir.net/voussoir/voussoir.net/commits/master/voussoir.net/writing/git_dot_voussoir_dot_net/git_dot_voussoir_dot_net.md"&gt;View this document's history&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://git.voussoir.net/voussoir/voussoir.net/commit/f93f5eebb89e0bce80206733fe752fb9d87e3ed3"&gt;&lt;time datetime="2023-09-18T21:03:43-07:00"&gt;2023-09-18&lt;/time&gt; Add git_dot_voussoir_dot_net.md.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;address&gt;Contact me: writing@voussoir.net&lt;/address&gt;
&lt;p&gt;If you would like to subscribe for more, add this to your RSS reader: &lt;a href="/writing/writing.atom" rel="alternate" type="application/atom+xml"&gt;https://voussoir.net/writing/writing.atom&lt;/a&gt;&lt;/p&gt;
&lt;/article&gt;</description><author>voussoir.net/writing</author><pubDate>Tue, 19 Sep 2023 07:03:43 GMT</pubDate><guid isPermaLink="true">https://voussoir.net/writing/git_dot_voussoir_dot_net</guid></item><item><title>My trip to the Communication and Laptop Museum in Estonia</title><link>https://ounapuu.ee/posts/2023/09/19/trip-to-communication-laptop-museum/</link><description>I ended up going to a small museum run by a passionate enthusiast in the beautiful countryside of south-east Estonia while on vacation, here are my notes on it.</description><author>./techtipsy</author><pubDate>Tue, 19 Sep 2023 06:00:00 GMT</pubDate><guid isPermaLink="true">https://ounapuu.ee/posts/2023/09/19/trip-to-communication-laptop-museum/</guid></item><item><title>Multiplexing the output of a process to multiple processes</title><link>https://heitorpb.github.io/bla/multiplexing-stdout/</link><description>How to feed the output of one process to multiple other processes simultaneously, using tee and Bash's process substitution.</description><author>Heitor's log</author><pubDate>Tue, 19 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://heitorpb.github.io/bla/multiplexing-stdout/</guid></item><item><title>How/Why to Sweep Async Tasks Under a Postgres Table</title><link>https://taylor.town/pg-task</link><description>I like slim and stupid servers, where each endpoint wraps a very dumb DB query. Dumb queries are fast. Fast queries make websites smooth and snappy. Keep those click/render loops sacred. Sweep complexity under a task table.</description><author>taylor.town</author><pubDate>Tue, 19 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/pg-task</guid></item><item><title>Vim tip 32: text and indent settings</title><link>https://learnbyexample.github.io/tips/vim-tip-32/</link><description>&lt;p&gt;Here are some text and indent Vim settings that you can put in the &lt;code&gt;vimrc&lt;/code&gt; file to customize your editor. See &lt;a href="https://vimhelp.org/options.txt.html"&gt;:h options.txt&lt;/a&gt; for complete reference.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;kbd&gt;filetype plugin indent on&lt;/kbd&gt; enables loading of &lt;code&gt;plugin&lt;/code&gt; and &lt;code&gt;indent&lt;/code&gt; files
&lt;ul&gt;
&lt;li&gt;these files become active based on the type of the file to influence syntax highlighting, indentation, etc&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;:echo $VIMRUNTIME&lt;/kbd&gt; gives your installation directory (&lt;code&gt;indent&lt;/code&gt; and &lt;code&gt;plugin&lt;/code&gt; directories would be present in this path)&lt;/li&gt;
&lt;li&gt;see &lt;a href="https://vimhelp.org/usr_05.txt.html#vimrc-filetype"&gt;:h vimrc-filetype&lt;/a&gt;, &lt;a href="https://vimhelp.org/filetype.txt.html#%3Afiletype-overview"&gt;:h :filetype-overview&lt;/a&gt; and &lt;a href="https://vimhelp.org/filetype.txt.html"&gt;:h filetype.txt&lt;/a&gt; for more details&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;set autoindent&lt;/kbd&gt; copy indent from the current line when starting a new line
&lt;ul&gt;
&lt;li&gt;useful for files not affected by &lt;code&gt;indent&lt;/code&gt; setting&lt;/li&gt;
&lt;li&gt;see also &lt;a href="https://vimhelp.org/options.txt.html#%27smartindent%27"&gt;:h smartindent&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;set textwidth=80&lt;/kbd&gt; guideline for Vim to automatically move to a new line with &lt;code&gt;80&lt;/code&gt; characters as the limit
&lt;ul&gt;
&lt;li&gt;white space is used to break lines, so a line can still be greater than the limit if there's no white space&lt;/li&gt;
&lt;li&gt;default is &lt;code&gt;0&lt;/code&gt; which disables this setting&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;set colorcolumn=80&lt;/kbd&gt; create a highlighted vertical bar at column number &lt;code&gt;80&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;use &lt;code&gt;highlight ColorColumn&lt;/code&gt; setting to customize the color for this vertical bar&lt;/li&gt;
&lt;li&gt;see &lt;a href="https://vi.stackexchange.com/q/574/1616"&gt;vi.stackexchange: Keeping lines to less than 80 characters&lt;/a&gt; for more details&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;set shiftwidth=4&lt;/kbd&gt; number of spaces to use for indentation (default is &lt;code&gt;8&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;set tabstop=4&lt;/kbd&gt; width for the tab character (default is &lt;code&gt;8&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;set expandtab&lt;/kbd&gt; use spaces for tab expansion&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;set cursorline&lt;/kbd&gt; highlight the line containing the cursor&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/vim_reference"&gt;Vim Reference Guide&lt;/a&gt; and &lt;a href="https://learnbyexample.github.io/curated_resources/vim.html"&gt;curated list of resources for Vim&lt;/a&gt;.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 19 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/vim-tip-32/</guid></item><item><title>F-Zero 99</title><link>http://pxtl.ca/2023/09/19/fzero99/</link><description>&lt;p&gt;i got a little carried away with this nostalgia game.  I loved the old F-Zero and this remake lets you play the classic against 98 other players at once.&lt;/p&gt;</description><author>Pxtl.ca</author><pubDate>Tue, 19 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">http://pxtl.ca/2023/09/19/fzero99/</guid></item><item><title>Fix runtimeerror: no .dist-info has pip in broken pipenv installs and virtualenv wheels</title><link>https://jdsalaro.com/howto/fix-pipenv-runtimeerror-no-dist-info-has-pip/</link><description>As mentioned in /howto/fix-pipenv-asdf-runtimeerror-no-dist-info-has-pip, I was initially confronted with issue some weeks ago but could attribute and fix the problem to asdf misbehaving. The issue, however, resurfaced and that was no longer the case. To my surprise, asdf seemed to be behaving as expected and was therefore of no relevance to the problem I was observing:If you are experiencing this problem and do not use asdf, this post might be of help as I managed to further narrow down its root cause.</description><author>Jayson Salazar Rodriguez | @jdsalaro | Blog</author><pubDate>Tue, 19 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jdsalaro.com/howto/fix-pipenv-runtimeerror-no-dist-info-has-pip/</guid></item><item><title>Software 3.0 and the AI Engineer Landscape (talk notes &amp;amp; slides)</title><link>https://www.swyx.io/ai-landscape</link><description>## 2024 update</description><author>swyx's site RSS Feed</author><pubDate>Mon, 18 Sep 2023 22:50:48 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/ai-landscape</guid></item><item><title>Thank you</title><link>https://letterstoanewdeveloper.com/2023/09/18/thank-you/</link><description>Dear reader, Welcome to the last letter. I couldn&amp;#8217;t have imagined what would happen when I penned my first letter 5 years ago. (I still think you should learn version control, for the record.) This blog outlasted two jobs. 280+ posts and 220,000+ visitors later, this project has changed my life in so many ways. &amp;#8230; &lt;a class="more-link" href="https://letterstoanewdeveloper.com/2023/09/18/thank-you/"&gt;Continue reading &lt;span class="screen-reader-text"&gt;Thank you&lt;/span&gt; &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;</description><author>Letters To A New Developer</author><pubDate>Mon, 18 Sep 2023 19:12:21 GMT</pubDate><guid isPermaLink="true">https://letterstoanewdeveloper.com/2023/09/18/thank-you/</guid></item><item><title>Automated Planning using Property-Directed Reachability with Seed Heuristics</title><link>https://tiim.ch/blog/2023-05-06-pdr-with-seed-heuristics</link><description>Masters Thesis. The goal of this thesis is to implement a pre-processing step to the Property Directed Reachability algorithm, to potentially improve the run-time performance. We use the pattern database heuristic to make use of the planning task structure for the seeding algorithm.</description><author>Tim Bachmann's Blog</author><pubDate>Mon, 18 Sep 2023 16:32:00 GMT</pubDate><guid isPermaLink="true">https://tiim.ch/blog/2023-05-06-pdr-with-seed-heuristics</guid></item><item><title>Sparkmaker FEP foil replacement</title><link>https://www.xythobuz.de/2023_09_18_sparkmaker_fep.html</link><description>&lt;p&gt;After a bunch of prints a hole appeared in the FEP foil of the resin vat in my &lt;a href="sparkmaker_repair.html"&gt;Sparkmaker printer&lt;/a&gt;.
So it was time for my first replacement.
I bought &lt;a href="https://fepshop.com/shop/materials/fepshop-fep-roll-127-120x26cm/"&gt;a roll of 127 micron FEP film from FEPshop.com&lt;/a&gt;.
The old official tutorial video for this replacement is still &lt;a href="https://www.youtube.com/watch?v=2HH5ARg0FUU"&gt;available on the Sparkmaker YouTube channel&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;First simply remove all screws and disassemble the three frame parts, the old foil and the rubber gasket.
Clean all parts with isopropyl alcohol.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/sparkmaker_fep_1.jpg"&gt;&lt;img alt="Old FEP film still installed (1)" class="pic" src="https://www.xythobuz.de/img/sparkmaker_fep_1_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/sparkmaker_fep_2.jpg"&gt;&lt;img alt="Old FEP film still installed (2)" class="pic" src="https://www.xythobuz.de/img/sparkmaker_fep_2_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/sparkmaker_fep_3.jpg"&gt;&lt;img alt="Close view of hole in old FEP film" class="pic" src="https://www.xythobuz.de/img/sparkmaker_fep_3_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;You can clearly see the hole in the top right of the vat in the pictures above.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/sparkmaker_fep_4.jpg"&gt;&lt;img alt="Short screws removed" class="pic" src="https://www.xythobuz.de/img/sparkmaker_fep_4_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/sparkmaker_fep_6.jpg"&gt;&lt;img alt="Long screws also removed" class="pic" src="https://www.xythobuz.de/img/sparkmaker_fep_6_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/sparkmaker_fep_5.jpg"&gt;&lt;img alt="All parts before cleaning" class="pic" src="https://www.xythobuz.de/img/sparkmaker_fep_5_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Cut a roughly matching piece of FEP film and pre-cut the holes after carefully aligning the first four corners.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/sparkmaker_fep_7.jpg"&gt;&lt;img alt="FEP film on middle frame part" class="pic" src="https://www.xythobuz.de/img/sparkmaker_fep_7_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/sparkmaker_fep_8.jpg"&gt;&lt;img alt="FEP film with screws on bottom frame part" class="pic" src="https://www.xythobuz.de/img/sparkmaker_fep_8_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/sparkmaker_fep_10.jpg"&gt;&lt;img alt="Fitting test of two frame parts" class="pic" src="https://www.xythobuz.de/img/sparkmaker_fep_10_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/sparkmaker_fep_9.jpg"&gt;&lt;img alt="All screws in place in bottom frame part and FEP film" class="pic" src="https://www.xythobuz.de/img/sparkmaker_fep_9_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The proper assembly order is the bottom-most part, followed by the FEP foil, then the gasket, then the middle and top parts.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/sparkmaker_fep_11.jpg"&gt;&lt;img alt="Gasket on FEP film" class="pic" src="https://www.xythobuz.de/img/sparkmaker_fep_11_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/sparkmaker_fep_12.jpg"&gt;&lt;img alt="Bottom two frame parts with FEP film and gasket (1)" class="pic" src="https://www.xythobuz.de/img/sparkmaker_fep_12_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/sparkmaker_fep_13.jpg"&gt;&lt;img alt="Bottom two frame parts with FEP film and gasket (2)" class="pic" src="https://www.xythobuz.de/img/sparkmaker_fep_13_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;I suspect the previous owner (and others in online forums) did not pre-cut holes in the FEP film before placing the screws.
So material of both the gasket and the FEP film was mangled up and pulled into some of the threaded holes.
In an ideal world you would nicely cut out the holes in their proper place.
I simply pushed a screwdriver through the holes in the frame, which worked fine.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/sparkmaker_fep_14.jpg"&gt;&lt;img alt="FEP film now tight" class="pic" src="https://www.xythobuz.de/img/sparkmaker_fep_14_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/sparkmaker_fep_15.jpg"&gt;&lt;img alt="After cutting the remaining parts from the edge" class="pic" src="https://www.xythobuz.de/img/sparkmaker_fep_15_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;I encountered some difficulties putting the long screws back to tighten the film.
A lot of force was required and I managed to rip out at least one turn of one of the threads.
But in the end all screws are still holding properly, though this may not be the case for many repeats of this procedure.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/sparkmaker_fep_16.jpg"&gt;&lt;img alt="Vat installed back into the printer" class="pic" src="https://www.xythobuz.de/img/sparkmaker_fep_16_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/sparkmaker_fep_17.jpg"&gt;&lt;img alt="First print with new film" class="pic" src="https://www.xythobuz.de/img/sparkmaker_fep_17_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;It seems to work fine.
No resin is leaking and the first print with the new film didn't have any problems.
At least not related to the FEP film.&lt;/p&gt;</description><author>xythobuz.de Blog</author><pubDate>Mon, 18 Sep 2023 15:00:00 GMT</pubDate><guid isPermaLink="true">https://www.xythobuz.de/2023_09_18_sparkmaker_fep.html</guid></item><item><title>Modelling Git Operations as Planning Problems</title><link>https://tiim.ch/blog/2021-01-git-operations-as-planning-problems</link><description>Bachelor Thesis. The goal of this thesis is to formally define a model of a subset of Git commands which mutate the revision graph, and to model those mutations as a planning task in the Planning Domain Definition Language. Multiple ways to model those graphs will be explored and those models will be compared by testing them using a set of planners.</description><author>Tim Bachmann's Blog</author><pubDate>Mon, 18 Sep 2023 14:41:51 GMT</pubDate><guid isPermaLink="true">https://tiim.ch/blog/2021-01-git-operations-as-planning-problems</guid></item><item><title>Suggested readings week 37/2023</title><link>https://www.stefanocosta.org/suggested-readings-week-37-2023/</link><description>&lt;hr /&gt;&lt;h3 id="privacy-nightmare-on-wheels"&gt;&lt;a href="https://foundation.mozilla.org/en/blog/privacy-nightmare-on-wheels-every-car-brand-reviewed-by-mozilla-including-ford-volkswagen-and-toyota-flunks-privacy-test/?ref=stefanocosta.org" rel="nofollow noreferrer noopener"&gt;Privacy Nightmare on Wheels&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Privacy Not Included (PNI) is an initiative by Mozilla global non-profit organization, that scrutinizes electronic and software products against potential privacy concerning problems.&lt;/p&gt;&lt;p&gt;In its latest edition a PNI bullettin reveals how twenty five car brands collect and share personal data. This includes sexual activity,&lt;/p&gt;</description><author>Food for Thought (by Stefano Costa)</author><pubDate>Mon, 18 Sep 2023 10:54:52 GMT</pubDate><guid isPermaLink="true">https://www.stefanocosta.org/suggested-readings-week-37-2023/</guid></item><item><title>The Midnight Library</title><link>https://arunmani.in/library/the-midnight-library/</link><description>Sometimes you think of your choices.</description><author>Arun Mani</author><pubDate>Mon, 18 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://arunmani.in/library/the-midnight-library/</guid></item><item><title>QA: The Final Frontier</title><link>https://www.craigpardey.com/post/2023-09-18-qa-strategy/</link><description>&lt;p&gt;QA organisations have come a long way in the last decade or two. In the early 2000s, automated unit testing was just gaining popularity and QA was performed manually by armies of testers. The QA process was time-consuming and arduous, and the testing timelines were the first thing to be compressed when development took longer than planned (as it almost always did).&lt;/p&gt;
&lt;p&gt;By the 2010s, many applications had moved to a web interface which allowed QA teams to automate some or all of the end-to-end testing using Selenium, or similar. This was a giant leap forward, especially for basic 3-tier apps with a web UI, a &amp;ldquo;services layer&amp;rdquo;, and a database. Everything was nicely contained in the one codebase, including the tests. Coupled with &lt;del&gt;Cruise Control&lt;/del&gt; &lt;del&gt;Hudson&lt;/del&gt; Jenkins, we had a reliable way to delivery quality software quickly.&lt;/p&gt;</description><author>Craig Pardey</author><pubDate>Mon, 18 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.craigpardey.com/post/2023-09-18-qa-strategy/</guid></item><item><title>Currently</title><link>https://benovermyer.com/blog/2023/09/currently/</link><description>&lt;p&gt;Last night I was poking around at the &lt;a href="https://indieweb.org/Homebrew_Website_Club" rel="external"&gt;Homebrew Website Club&lt;/a&gt; and
discovered a site with a "/currently" page. It's just a little status update along the lines of the old
Livejournal tags. I thought this was clever, so I added something like it to my own site.&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Mon, 18 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2023/09/currently/</guid></item><item><title>"Help, iterators made my Rust program slower!"</title><link>https://ntietz.com/blog/rust-iterators-and-threads/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;Recently in a programming community I belong to, someone presented a problem.
They had a Rust program which was using threads and for loops.
When they updated the code to use iterators, it got dramatically slower.
Why did this happen?&lt;/p&gt;
&lt;p&gt;For a Rust veteran, the problem might not be surprising, but it trips up a lot of people because of how iterators work.
Let's set the stage first with an example program.&lt;/p&gt;
&lt;p&gt;Here's a program similar to what they presented originally.
Instead of doing real work, though, we'll just use sleeps.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #fa5c4b;"&gt;use &lt;/span&gt;&lt;span&gt;std::thread;
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;use &lt;/span&gt;&lt;span&gt;std::time;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;do_work&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;i&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;usize&lt;/span&gt;&lt;span&gt;) -&amp;gt; thread::JoinHandle&amp;lt;()&amp;gt; {
&lt;/span&gt;&lt;span&gt;    thread::spawn(&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;move &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;|| &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; duration &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span&gt;time::Duration::from_millis(&lt;/span&gt;&lt;span style="color: #d3869b;"&gt;100&lt;/span&gt;&lt;span&gt;);
&lt;/span&gt;&lt;span&gt;        thread::sleep(duration);
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;println!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;thread &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;{i}&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt; done&amp;quot;&lt;/span&gt;&lt;span&gt;);
&lt;/span&gt;&lt;span&gt;    })
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;main&lt;/span&gt;&lt;span&gt;() {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let mut&lt;/span&gt;&lt;span&gt; handles &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Vec&lt;/span&gt;&lt;span&gt;::new();
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;for&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;in &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;..&lt;/span&gt;&lt;span style="color: #d3869b;"&gt;10 &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; handle &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;do_work&lt;/span&gt;&lt;span&gt;(i);
&lt;/span&gt;&lt;span&gt;        handles.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;push&lt;/span&gt;&lt;span&gt;(handle);
&lt;/span&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;for&lt;/span&gt;&lt;span&gt; handle &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;in&lt;/span&gt;&lt;span&gt; handles {
&lt;/span&gt;&lt;span&gt;        handle.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;join&lt;/span&gt;&lt;span&gt;();
&lt;/span&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When I run this one on my machine, it takes 103 milliseconds.&lt;/p&gt;
&lt;p&gt;Now let's see it using iterators, in a way you might expect to work.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #fa5c4b;"&gt;use &lt;/span&gt;&lt;span&gt;std::thread;
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;use &lt;/span&gt;&lt;span&gt;std::time;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;do_work&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;i&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;usize&lt;/span&gt;&lt;span&gt;) -&amp;gt; thread::JoinHandle&amp;lt;()&amp;gt; {
&lt;/span&gt;&lt;span&gt;    thread::spawn(&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;move &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;|| &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; duration &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span&gt;time::Duration::from_millis(&lt;/span&gt;&lt;span style="color: #d3869b;"&gt;100&lt;/span&gt;&lt;span&gt;);
&lt;/span&gt;&lt;span&gt;        thread::sleep(duration);
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;println!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;thread &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;{i}&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt; done&amp;quot;&lt;/span&gt;&lt;span&gt;);
&lt;/span&gt;&lt;span&gt;    })
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;main&lt;/span&gt;&lt;span&gt;() {
&lt;/span&gt;&lt;span&gt;    (&lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;..&lt;/span&gt;&lt;span style="color: #d3869b;"&gt;10&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;        .&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;map&lt;/span&gt;&lt;span&gt;(do_work)
&lt;/span&gt;&lt;span&gt;        .&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;for_each&lt;/span&gt;&lt;span&gt;(|&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;handle&lt;/span&gt;&lt;span&gt;| {
&lt;/span&gt;&lt;span&gt;            handle.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;join&lt;/span&gt;&lt;span&gt;();
&lt;/span&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And this one takes... 1008 milliseconds!
It takes 10 times longer.
It's easier to read in a lot of ways, because it doesn't require separately keeping track of the join handles, but it's so much slower.
Why?&lt;/p&gt;
&lt;p&gt;The clue is in being nearly exactly 10 times longer.
That's suspiciously similar to the number of things we're iterating over for a good reason: because we have lost all parallelism here.&lt;/p&gt;
&lt;p&gt;In Rust, &lt;a href="https://doc.rust-lang.org/std/iter/index.html#laziness"&gt;iterators are lazy&lt;/a&gt;, which means that nothing happens with them until &lt;code&gt;next&lt;/code&gt; is called on it, or it's iterated over (same thing, really).
This lets you do really neat things, like create an infinite-length iterator which you zip with a finite-length iterator (this can be a way to implement &lt;a href="https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.enumerate"&gt;&lt;code&gt;enumerate&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The code above chains together a few iterators.
First, we have &lt;code&gt;(0..10)&lt;/code&gt;, which creates a &lt;code&gt;Range&lt;/code&gt;, which is an iterator over a particular range of numbers.
Then we call &lt;code&gt;.map&lt;/code&gt; on it, which transforms it into an iterator which will have a number for each iteration and call &lt;code&gt;do_work&lt;/code&gt; on that number.
The first iterator isn't evaluated, but is &lt;em&gt;transformed&lt;/em&gt;: when evaluated, it won't create the numbers in one go, then the threads in another; it will just do all the work for each iteration one step at a time.
And then the final step is we call &lt;code&gt;for_each&lt;/code&gt; on it.
This returns nothing and does iterate over the underlying iterator.
But as we've noted, it doesn't collect the elements of the iterator then iterate over them: it applies its closure to each element individually in turn.&lt;/p&gt;
&lt;p&gt;So here we're really doing this:&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #fa5c4b;"&gt;for&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;in &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;..&lt;/span&gt;&lt;span style="color: #d3869b;"&gt;10 &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; handle &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;do_work&lt;/span&gt;&lt;span&gt;(i);
&lt;/span&gt;&lt;span&gt;    handle.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;join&lt;/span&gt;&lt;span&gt;();
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And so because we create the handle then &lt;em&gt;immediately join it&lt;/em&gt;, we never achieve any parallelism and it's much slower!&lt;/p&gt;
&lt;p&gt;In this sort of program, for loops are pretty idiomatic.
But you can still write it with iterators if that's more your speed, you just have to do it a little differently.
Omitting the repeated definition of &lt;code&gt;do_work&lt;/code&gt;, here's an example of that.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #fa5c4b;"&gt;fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;main&lt;/span&gt;&lt;span&gt;() {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; handles: &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Vec&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;_&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;..&lt;/span&gt;&lt;span style="color: #d3869b;"&gt;10&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;map&lt;/span&gt;&lt;span&gt;(do_work).&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;collect&lt;/span&gt;&lt;span&gt;();
&lt;/span&gt;&lt;span&gt;    handles.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;into_iter&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;for_each&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;move &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;|&lt;/span&gt;&lt;span&gt;handle&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;| &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;        handle.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;join&lt;/span&gt;&lt;span&gt;();
&lt;/span&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is admittedly much wordier.
But critically, it does allow using iterators here and still achieving parallelism.
The key is that creating the join handles, and joining on them, are separated into two distinct steps which each consume the underlying iterators.
(A side note: that last &lt;code&gt;for_each&lt;/code&gt; would be &lt;em&gt;much&lt;/em&gt; cleaner as a simple for loop, but I wanted to demonstrate this. Don't do this, probably.)&lt;/p&gt;
&lt;p&gt;And there you have it!
If your code is a lot slower when you use iterators, this might be why.&lt;/p&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 18 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/rust-iterators-and-threads/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Stupid by choice</title><link>https://alexmolas.com/2023/09/18/embracing-dumbness.html</link><description>Discover the surprising wisdom in embracing 'dumbness.' Learn from a remarkable grandmother-in-law who mastered the art of asking for help by appearing clueless. Explore the benefits of reducing cognitive load, saving time, and focusing on your priorities. Uncover when to apply this strategy and when it's best to rely on your expertise.</description><author>Alex Molas Blog</author><pubDate>Mon, 18 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://alexmolas.com/2023/09/18/embracing-dumbness.html</guid></item><item><title>Switching to wildcard certificates for certbot with Porkbun</title><link>https://b.yuxuan.org/certbot-dns-porkbun</link><description>&lt;p&gt;&lt;em&gt;(If this reappears in your RSS reader as new, my apologise: There was a typo in the original URL that was fixed, as a result this post would appear as new with a new URL.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Over the weekend there was a &lt;a href="https://news.ycombinator.com/item?id=37531801"&gt;HN thread&lt;/a&gt; discussing about Certificate Transparency logs, which prompted me to &lt;a href="https://fosstodon.org/@fishy/111075759307415447"&gt;toot this&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;While switching to wildcard certificates doesn&amp;rsquo;t &lt;em&gt;really&lt;/em&gt; hide your subdomains, it &lt;em&gt;helps&lt;/em&gt; to some degree, so I decided to switch my certs to wildcards.&lt;/p&gt;

&lt;p&gt;Wildcard certs requires dns challenge from &lt;a href="https://certbot.eff.org/"&gt;certbot&lt;/a&gt;. There are some dns plugins for certbot that&amp;rsquo;s packaged with most distros (including Debian) but those does not include &lt;a href="https://porkbun.com/"&gt;Porkbun&lt;/a&gt;, my choice of dns provider. There&amp;rsquo;s a &lt;a href="https://github.com/infinityofspace/certbot_dns_porkbun"&gt;third party python porkbun dns plugin&lt;/a&gt; recommended by the official doc, but since that is not packaged by Debian, you have to use &lt;code&gt;sudo pip3 install&lt;/code&gt; to install it to be able to be recognized by certbot installed via Debian, and Debian has some huge warning against using &lt;code&gt;sudo pip3 install&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Since I &lt;a href="https://b.yuxuan.org/porkbun-dyndns-go"&gt;already wrote some go code with Porkbun API&lt;/a&gt;, and certbot provided good documentation on &lt;a href="https://eff-certbot.readthedocs.io/en/stable/using.html#manual"&gt;how to do dns challenges &amp;ldquo;manually&amp;rdquo;&lt;/a&gt;, I decided to write another go code to do this instead. So that I can have a single statically compiled go binary, with up/down scripts, that can be put on any machine running certbot to handle the task.&lt;/p&gt;

&lt;p&gt;The code is at &lt;a href="https://github.com/fishy/certbot-dns-porkbun"&gt;Github repo here&lt;/a&gt;, I just put the compiled binary (&lt;code&gt;certbot-dns-porkbun&lt;/code&gt;) with the 2 scripts (&lt;code&gt;authenticator.sh&lt;/code&gt; and &lt;code&gt;cleanup.sh&lt;/code&gt;) into the same directory (I just put them under &lt;code&gt;/etc/letsencrypt/porkbun&lt;/code&gt;), then run this to get wildcard certs:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-sh"&gt;sudo certbot certonly \
  -n \
  --agree-tos \
  --manual \
  --preferred-challenges=dns \
  --manual-auth-hook /etc/letsencrypt/porkbun/authenticator.sh \
  --manual-cleanup-hook /etc/letsencrypt/porkbun/cleanup.sh \
  -d yuxuan.org \
  -d *.yuxuan.org
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Note that in order to auto restart/reload your http server after renewal (in order to pick up the updated certs), you need a deploy hook. I just put a script under &lt;code&gt;/etc/letsencrypt/renewal-hooks/deploy/&lt;/code&gt; to reload nginx:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-sh"&gt;$ cat /etc/letsencrypt/renewal-hooks/deploy/nginx.sh
#!/bin/sh

exec systemctl reload nginx
&lt;/code&gt;&lt;/pre&gt;</description><author>La Vita è Bear</author><pubDate>Mon, 18 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://b.yuxuan.org/certbot-dns-porkbun</guid></item><item><title>Chesterton's Invisible Fences</title><link>https://bastian.rieck.me/blog/2023/chesterton/</link><description>&lt;p&gt;I am well aware of the principle of &lt;a href="https://en.wiktionary.org/wiki/Chesterton%27s_fence"&gt;Chesterton&amp;rsquo;s
Fence&lt;/a&gt;, i.e. the
principle that &amp;rsquo;no reforms should be attempted until the
justification of the &lt;em&gt;status quo&lt;/em&gt; is understood.&amp;rsquo; Over the years, this
principle has served me well, in particular when it comes to software
development and academic matters. However, I recently discovered that
sometimes, the fence is invisible, so you ignore it unknowingly and
wreak havoc.&lt;/p&gt;
&lt;p&gt;The exact details are not super relevant here, but here is a brief
outline of what happened: when organising the reviewing and submission
process for &lt;a href="https://sampta2023.github.io"&gt;SampTA&lt;/a&gt;, the conference on
Sampling Theory and Applications, I set up things the way I was used to
from the machine learning community. We thus had double-blind reviews
and double-blind submissions, as well as a programme committee that was
used to review machine learning submission. Little did I know that this
was not the way things are usually done in that community. Thus, while
my co-organisers and I were well-meaning, we just irritated and confused
authors unduly. Even when it came to creating the programme itself, we
ignored several prominent invisible fences and let reviewers decide on
special presentation slots&amp;mdash;little did we know that participants were
expecting rather &lt;em&gt;more&lt;/em&gt; talks so as to be able to connect to a broader
community.&lt;/p&gt;
&lt;p&gt;How did this happen? Partially, it can be explained by a breakdown in
communications; due to the personal circumstances of some colleagues,
I had to step in on short notice, and everyone assumed that I had been
briefed about how this community functions. I had not, though. Together
with the proclivity of academic committees to sit unmoving until forced
to move by a strict deadline, I always received the feedback well after
the decision had already been made. Unwittingly, I had thus become
a blind reformer, confusing and irritating the community.&lt;/p&gt;
&lt;p&gt;All is well now, after some explanatory talks, but the lesson is
still very fresh: &lt;strong&gt;sometimes, the fence is invisible&lt;/strong&gt;.&lt;/p&gt;</description><author>Ecce Homology on Bastian Grossenbacher Rieck's personal homepage</author><pubDate>Sun, 17 Sep 2023 17:00:00 GMT</pubDate><guid isPermaLink="true">https://bastian.rieck.me/blog/2023/chesterton/</guid></item><item><title>State of Stateless - DebConf 2023</title><link>https://arunmani.in/articles/state-of-stateless/</link><description>My talk about immutability, reproducibility and declarative system in Debian at DebConf 2023.</description><author>Arun Mani</author><pubDate>Sun, 17 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://arunmani.in/articles/state-of-stateless/</guid></item><item><title>my battlestation</title><link>https://taylor.town/my-battlestation</link><description>...in case you were wondering.</description><author>taylor.town</author><pubDate>Sun, 17 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/my-battlestation</guid></item><item><title>Refactoring from feature specs to system specs</title><link>https://railsnotes.xyz/blog/feature-specs-to-system-specs</link><description>In this article, I break down the difference between system specs and feature specs in RSpec and Rails, and walk you through refactoring your feature specs to system specs.</description><author>RailsNotes, the Ruby on Rails guides you wished you had.</author><pubDate>Sun, 17 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://railsnotes.xyz/blog/feature-specs-to-system-specs</guid></item><item><title>Climbing the three peaks (Snowdon, Scafell Pike and Ben Nevis)</title><link>https://michael-lewis.com/posts/climbing-the-three-peaks-snowdon-scafell-pike-and-ben-nevis/</link><description>&lt;h2 id="introduction"&gt;
  Introduction
  &lt;a class="heading-link" href="#introduction"&gt;
    &lt;i class="fa-solid fa-link" title="Link to heading"&gt;&lt;/i&gt;
    &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;
  &lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The &amp;ldquo;three peaks&amp;rdquo; in this context are the three highest peaks in Great Britain&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Scafell Pike, England, 978m&lt;/li&gt;
&lt;li&gt;Snowdon (Yr Wyddfa in Welsh), Wales, 1085m&lt;/li&gt;
&lt;li&gt;Ben Nevis (Bheinn Nibheis in Scottish Gaelic), Scotland, 1345m&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some people do the &amp;ldquo;&lt;a class="external-link" href="https://officialthreepeakschallenge.co.uk/national-3-peaks-how-it-works/" rel="noopener" target="_blank"&gt;national three peaks challenge&lt;/a&gt;&amp;rdquo;, which is to climb all three in a short space of time (usually 24 hours, sometimes 3 days) but I did it over 3 years: Snowden in 2021, Scafell Pike in 2022 and Ben Nevis in 2023.&lt;/p&gt;</description><author>Michael Ian Lewis</author><pubDate>Sun, 17 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://michael-lewis.com/posts/climbing-the-three-peaks-snowdon-scafell-pike-and-ben-nevis/</guid></item><item><title>Don't read everything you believe</title><link>https://mteam.space/posts/dont-read-everything-you-believe/</link><description>Polarization, tribalism, and the echo chamber.</description><author>mteam's Writings</author><pubDate>Sun, 17 Sep 2023 00:39:33 GMT</pubDate><guid isPermaLink="true">https://mteam.space/posts/dont-read-everything-you-believe/</guid></item><item><title>Game Design: On Realism vs Fun, and Sim vs Play</title><link>https://synystron.substack.com/p/game-design-on-realism-vs-fun-and</link><description>I loved the *idea* of The Sims.</description><author>Synystron Synlogica</author><pubDate>Sat, 16 Sep 2023 21:35:25 GMT</pubDate><guid isPermaLink="true">https://synystron.substack.com/p/game-design-on-realism-vs-fun-and</guid></item><item><title>Improving your online privacy: An update</title><link>https://blog.ovalerio.net/archives/2615</link><description>Ten years ago, after it became clear to almost everyone that all our online activity was being tracked and stored, I wrote a blog post about simple steps a person could take to improve their privacy online. Essentially, it contains a few recommendations that everyone could follow to reduce their fingerprint without much effort. It [&amp;#8230;]</description><author>Gonçalo Valério</author><pubDate>Sat, 16 Sep 2023 21:22:55 GMT</pubDate><guid isPermaLink="true">https://blog.ovalerio.net/archives/2615</guid></item><item><title>Using open-interpreter to create a DIY audiobook with say</title><link>https://www.danielcorin.com/til/say/diy_audio_book/</link><description>Using open-interpreter to create a DIY audiobook with say</description><author>Thought Eddies</author><pubDate>Sat, 16 Sep 2023 20:03:25 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/say/diy_audio_book/</guid></item><item><title>Use your database to power state machines</title><link>/state-machines/</link><description>&lt;p&gt;Most people are familiar with state machines and know their value. The average
state machine library can help you model states, prevent invalid transitions,
and produce diagrams that help even non-technical people understand how the code
behaves.&lt;/p&gt;

&lt;p&gt;This article isn’t about making the case for state machines. It’s about how you
take the concept of a state machine and have it work alongside your database
models, leveraging your relational database (say Postgres or MySQL) to help you
build concurrent-safe and efficient software.&lt;/p&gt;

&lt;p&gt;I first encountered this pattern when I joined GoCardless in 2015. Processing
bank payments is a multi-day affair and extremely stateful, so it’s no surprise
that the team eventually built a library called &lt;a href="https://github.com/gocardless/statesman"&gt;Statesman&lt;/a&gt; that
provided a state machine powered by underlying transition tables.&lt;/p&gt;

&lt;p&gt;Most of GoCardless’ critical processes use Statesman, and by the time I left we
had transition tables with well over 10B rows. It became such an essential tool
that I’d argue it was a powerful competitive advantage. Statesman is even
reflected externally, such as in the GoCardless public API with endpoints
providing rich audit trails.&lt;/p&gt;

&lt;p&gt;So if you use Ruby, grab Statesman and get going. But for those who want these
benefits but are non-Ruby’ers, you can implement a small library in your
language of choice in just a few hours provided you understand the nuances of
transition tables, locking, and edge cases.&lt;/p&gt;

&lt;p&gt;That’s what I did the other day at incident.io. Here’s a guide so you can do it
too.&lt;/p&gt;

&lt;h2 id="transition-tables"&gt;Transition tables&lt;/h2&gt;

&lt;p&gt;Let’s begin by saying that in most applications, you want to be capturing every
transition that a resource has made through a state machine and storing it for
later analysis. In some situations, you may even consider the history of
transitions to decide which state to transition to next.&lt;/p&gt;

&lt;p&gt;That’s why the first component of any database state machine will be creating a
table to contain transitions.&lt;/p&gt;

&lt;div class="language-sql highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Assuming the payments table is already here.&lt;/span&gt;
&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;payments&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;payment_transitions&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="n"&gt;generate_ulid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;payment_id&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;references&lt;/span&gt; &lt;span class="n"&gt;payments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;to_state&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;most_recent&lt;/span&gt; &lt;span class="nb"&gt;boolean&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;sort_key&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Each transition row states:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The parent resource it belongs to (&lt;code class="language-plaintext highlighter-rouge"&gt;payment_id&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;The state this transition is moving into (&lt;code class="language-plaintext highlighter-rouge"&gt;to_state&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;Whether this transition is the latest (&lt;code class="language-plaintext highlighter-rouge"&gt;most_recent&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;An ordinal to allow for logical ordering of transitions (&lt;code class="language-plaintext highlighter-rouge"&gt;sort_key&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now we add two index-backed unique constraints that are going to ensure our
state machine’s integrity.&lt;/p&gt;

&lt;div class="language-sql highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;unique&lt;/span&gt; &lt;span class="k"&gt;index&lt;/span&gt; &lt;span class="n"&gt;idx_payment_transitions_by_parent_most_recent&lt;/span&gt;
&lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;payment_transitions&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;btree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;most_recent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;most_recent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;unique&lt;/span&gt; &lt;span class="k"&gt;index&lt;/span&gt; &lt;span class="n"&gt;idx_payment_transitions_by_parent_sort_key&lt;/span&gt;
&lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;payment_transitions&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;btree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort_key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The first ensures we can only ever have a single transition that is
&lt;code class="language-plaintext highlighter-rouge"&gt;most_recent&lt;/code&gt; for any payment, a clear requirement if we ever want to sensibly
ask “what state is this payment in?”. The second ensures we get no duplicates on
&lt;code class="language-plaintext highlighter-rouge"&gt;sort_key&lt;/code&gt;: less important, but useful to ensure all transitions can be strictly
ordered.&lt;/p&gt;

&lt;h2 id="expressing-the-state-machine-in-code"&gt;Expressing the state machine in code&lt;/h2&gt;

&lt;p&gt;Now we have a table we’ll use to store transitions, we need to build a library
that can be used to express the state machine in our codebase.&lt;/p&gt;

&lt;p&gt;In the Go library we’re using at incident.io, we start by expressing the
transition table as a domain model that our ORM can work with:&lt;/p&gt;

&lt;div class="language-go highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;PaymentTransition&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;// ID is the unique ID of this transition&lt;/span&gt;
  &lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"id" gorm:"type:text;primaryKey;default:generate_ulid()"`&lt;/span&gt;
  &lt;span class="c"&gt;// PaymentID is a reference to the parent resource&lt;/span&gt;
  &lt;span class="n"&gt;PaymentID&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"payment_id"`&lt;/span&gt;
  &lt;span class="c"&gt;// ToState is where this transition was to&lt;/span&gt;
  &lt;span class="n"&gt;ToState&lt;/span&gt; &lt;span class="n"&gt;PaymentState&lt;/span&gt; &lt;span class="s"&gt;`json:"to_state"`&lt;/span&gt;
  &lt;span class="c"&gt;// MostRecent is true when this transition is the most recent&lt;/span&gt;
  &lt;span class="n"&gt;MostRecent&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="s"&gt;`json:"most_recent"`&lt;/span&gt;
  &lt;span class="c"&gt;// SortKey provides ordinality over transitions&lt;/span&gt;
  &lt;span class="n"&gt;SortKey&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="s"&gt;`json:"sort_key"`&lt;/span&gt;
  &lt;span class="c"&gt;// CreatedAt is set upon transition creation&lt;/span&gt;
  &lt;span class="n"&gt;CreatedAt&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt; &lt;span class="s"&gt;`json:"created_at"`&lt;/span&gt;
  &lt;span class="c"&gt;// UpdatedAt is set whenever this transition is modified&lt;/span&gt;
  &lt;span class="n"&gt;UpdatedAt&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt; &lt;span class="s"&gt;`json:"updated_at"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PaymentTransition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;Payment&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Payment&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;PaymentTransition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;PaymentState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToState&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// ParentColumn tells our machine library what column refers&lt;/span&gt;
&lt;span class="c"&gt;// to the parent resource.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PaymentTransition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ParentColumn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;structField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"PaymentID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"payment_id"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You’ll notice &lt;code class="language-plaintext highlighter-rouge"&gt;PaymentState&lt;/code&gt; is a new type, which we define as a Go enum value:&lt;/p&gt;

&lt;div class="language-go highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;PaymentState&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;PaymentStatePendingSubmission&lt;/span&gt; &lt;span class="n"&gt;PaymentState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"pending_submission"&lt;/span&gt;
  &lt;span class="n"&gt;PaymentStateSubmitted&lt;/span&gt;         &lt;span class="n"&gt;PaymentState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"submitted"&lt;/span&gt;
  &lt;span class="n"&gt;PaymentStatePaid&lt;/span&gt;              &lt;span class="n"&gt;PaymentState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"paid"&lt;/span&gt;
  &lt;span class="n"&gt;PaymentStateCancelled&lt;/span&gt;         &lt;span class="n"&gt;PaymentState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"cancelled"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally, we have the parent resource (&lt;code class="language-plaintext highlighter-rouge"&gt;Payment&lt;/code&gt;) implement a &lt;code class="language-plaintext highlighter-rouge"&gt;Machine()&lt;/code&gt; method
that ties all this together and tells our library about valid transition paths:&lt;/p&gt;

&lt;div class="language-go highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Payment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Machine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Machine&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;PaymentTransition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PaymentState&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;PaymentTransition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PaymentState&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PaymentStatePendingSubmission&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;PaymentStateSubmitted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PaymentStateSubmitted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;PaymentStatePaid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PaymentStateCancelled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;machine.New&lt;/code&gt; builds a config struct that records each of the states and where
they are permitted to transition into. In our example, that means
&lt;code class="language-plaintext highlighter-rouge"&gt;pending_submission&lt;/code&gt; is allowed to transition to &lt;code class="language-plaintext highlighter-rouge"&gt;submitted&lt;/code&gt;, but not to &lt;code class="language-plaintext highlighter-rouge"&gt;paid&lt;/code&gt;
or &lt;code class="language-plaintext highlighter-rouge"&gt;cancelled&lt;/code&gt;.&lt;/p&gt;

&lt;div class="language-go highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;machine&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;comparable&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;states&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id="executing-a-transition"&gt;Executing a transition&lt;/h2&gt;

&lt;p&gt;Our code knows everything it needs to model transitions now, and we can
transition a payment like so:&lt;/p&gt;

&lt;div class="language-go highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="n"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Machine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
  &lt;span class="n"&gt;TransitionTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PaymentStatePendingSubmission&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
  &lt;span class="n"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Really simple, but what does this give us? Why is using our database for this
better than simply doing things in-memory?&lt;/p&gt;

&lt;p&gt;Well, even when there are concurrent processes transitioning the same payment,
the transition will only be created if the source -&amp;gt; destination is permitted in
our configuration. That’s extremely useful for most modern applications which
have many processes working at once, but want their developers to write code
that acts as if it’s a single-process system.&lt;/p&gt;

&lt;p&gt;For &lt;code class="language-plaintext highlighter-rouge"&gt;Execute()&lt;/code&gt; to provide this guarantee you need to know how your database is
configured and be careful about how you take locks. But assuming you’re running
Postgres in &lt;code class="language-plaintext highlighter-rouge"&gt;read committed&lt;/code&gt; mode (the default) then you can copy this code and
know you’re safe.&lt;/p&gt;

&lt;p&gt;In SQL, as that’s what &lt;code class="language-plaintext highlighter-rouge"&gt;Execute()&lt;/code&gt; is really running, the function looks like
so:&lt;/p&gt;

&lt;div class="language-sql highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- (1)&lt;/span&gt;
&lt;span class="k"&gt;update&lt;/span&gt; &lt;span class="n"&gt;payment_transitions&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="n"&gt;most_recent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'f'&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;most_recent&lt;/span&gt;
&lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;payment_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'PM123'&lt;/span&gt;
&lt;span class="n"&gt;returning&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- (2)&lt;/span&gt;
&lt;span class="cm"&gt;/* use previous result to validate transition from -&amp;gt; to */&lt;/span&gt;

&lt;span class="c1"&gt;-- (3)&lt;/span&gt;
&lt;span class="k"&gt;insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;payment_transitions&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;payment_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;most_recent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to_state&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s1"&gt;'PM123'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="cm"&gt;/* previous sort_key + 10 */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'submitted'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The first update (1) will lock the previously most recent transition, and block
any competing transactions (this means they halt until the lock is released).&lt;/p&gt;

&lt;p&gt;The first to win is returned the previous transition and can check that moving
from the previous state to the next desired state is permitted, and will
rollback the transaction if it is not. In the Go implementation I shared above,
that means checking the &lt;code class="language-plaintext highlighter-rouge"&gt;Config&lt;/code&gt; struct to make sure this is permitted.&lt;/p&gt;

&lt;p&gt;Finally, if the transition is valid, we’ll insert a new transition row for the
destination state with &lt;code class="language-plaintext highlighter-rouge"&gt;most_recent&lt;/code&gt; set and &lt;code class="language-plaintext highlighter-rouge"&gt;sort_key&lt;/code&gt; advanced from the
previous transition row, relying on the unique indexes to catch any conflicts.&lt;/p&gt;

&lt;p&gt;That’s how a successful transaction that was the first to attempt a transition
will run, and any subsequent transactions will follow one of two paths:&lt;/p&gt;

&lt;h3 id="a-initial-transition-fails"&gt;(a) Initial transition fails&lt;/h3&gt;

&lt;p&gt;If the first transaction fails then we rollback, release the lock, the competing
transaction acquires it and proceeds as if the first transaction never occurred.&lt;/p&gt;

&lt;p&gt;This reduces to “start again”.&lt;/p&gt;

&lt;h3 id="b-initial-transition-succeeded"&gt;(b) Initial transition succeeded&lt;/h3&gt;

&lt;p&gt;If the first transaction suceeded, the update will unlock but return zero rows
from a read committed Postgres.&lt;/p&gt;

&lt;p&gt;This looks to us like either (i) there was no transition and we should assume
this is will be the initial entry for this parent or (ii) we’ve been beaten by a
competing transaction.&lt;/p&gt;

&lt;p&gt;We proceed by pretending like it’s the initial transition as in the case of:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;(i) We’d be correct, and we create the initial transition successfully.&lt;/li&gt;
  &lt;li&gt;(ii) We’re about to try creating another &lt;code class="language-plaintext highlighter-rouge"&gt;most_recent&lt;/code&gt; transition and our
insert will fail against the &lt;code class="language-plaintext highlighter-rouge"&gt;most_recent&lt;/code&gt; unique index.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="ergonomic-apis"&gt;Ergonomic APIs&lt;/h2&gt;

&lt;p&gt;Developers who use the machine APIs benefit from the concurrency protections
without needing to think too much about them.&lt;/p&gt;

&lt;p&gt;When we hit conflicts, the library returns &lt;code class="language-plaintext highlighter-rouge"&gt;ErrTransitionConflict&lt;/code&gt; and we have a
helper that retries if this error is returned. That means you can ‘solve’
concurrent processes racing against each other by just wrapping your code in a
retry, which would otherwise require someone to think carefully about their
concurrency model and bring that complexity into their normal application code.&lt;/p&gt;

&lt;p&gt;With all your transitions in the database, it becomes efficient and easy to find
all X in state Y by leveraging database indexes. The API and resulting SQL looks
like this:&lt;/p&gt;

&lt;div class="language-go highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="c"&gt;/*
select
   *
from payments
join
  payment_transitions
  on payments.id = payment_transitions.payment_id
  and most_recent = 't';
*/&lt;/span&gt;
&lt;span class="n"&gt;payments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewQuerier&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Payment&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
  &lt;span class="n"&gt;Joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InState&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PaymentTransition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Payment&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PaymentStatePendingSubmission&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
  &lt;span class="n"&gt;Find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We’ve also allowed &lt;code class="language-plaintext highlighter-rouge"&gt;TransitionTo&lt;/code&gt; to receive arbitrary partials that set
auxiliary fields on transition tables, so that you can track associated
metadata against transitions that need them.&lt;/p&gt;

&lt;p&gt;An example would be to attach a submission ID whenever you move a payment to
submitted, where that will set a &lt;code class="language-plaintext highlighter-rouge"&gt;submission_id&lt;/code&gt; column on the resulting
transition row:&lt;/p&gt;

&lt;div class="language-go highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="n"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Machine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
  &lt;span class="n"&gt;TransitionTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PaymentStatePendingSubmission&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
  &lt;span class="n"&gt;With&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PaymentTransitionBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubmissionID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubmissionID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id="just-try-it"&gt;Just try it&lt;/h2&gt;

&lt;p&gt;I’ve use banking examples in this post, but don’t think this is limited to
payments. We use this at incident.io to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Power incident updates, where each update has a message, severity, status etc
and &lt;code class="language-plaintext highlighter-rouge"&gt;incident_updates&lt;/code&gt; is the transition table.&lt;/li&gt;
  &lt;li&gt;Track the status of alert sources so we can record when something was setup,
activated, or deactivated and why that happened.&lt;/li&gt;
  &lt;li&gt;Our nudges feature creates ‘nudge runs’ that are transitioned as we work them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Provided you follow the instructions I’ve given, you’ll inherit the concurrency
protections and if you try it out, I promise you’ll soon realise their value.
Even if it’s when your Data team notice how easy it is to build their models,
because there’s much richer data available and arranged consistently than you
have before.&lt;/p&gt;

&lt;p&gt;Low cost, high upside. Give it a shot.&lt;/p&gt;</description><author>Lawrence Jones</author><pubDate>Sat, 16 Sep 2023 15:00:00 GMT</pubDate><guid isPermaLink="true">/state-machines/</guid></item><item><title>Setting Up a Terraform Pipeline in Azure DevOps</title><link>https://sergeykibish.com/blog/setup-a-terraform-pipeline-in-azure-devops/</link><description>Are you ready to automate your Terraform deployments using Azure DevOps? In this short post, I'll walk you through creating a Terraform pipeline using Azure YAML Pipelines.</description><author>Sergey's Blog</author><pubDate>Sat, 16 Sep 2023 10:16:03 GMT</pubDate><guid isPermaLink="true">https://sergeykibish.com/blog/setup-a-terraform-pipeline-in-azure-devops/</guid></item><item><title>The Photo Game: Wildcard</title><link>https://andrewbridges.org/the-photo-game-wildcard/</link><description>My wife and I have been planning a trip to the zoo for weeks. We were excited to take our daughter, who's been diligently practicing her animal sounds daily.</description><author>Show Notes</author><pubDate>Sat, 16 Sep 2023 06:59:46 GMT</pubDate><guid isPermaLink="true">https://andrewbridges.org/the-photo-game-wildcard/</guid></item><item><title>Emacs hangs saving .authinfo.gpg (workaround)</title><link>https://xenodium.com/emacs-hangs-saving-authinfogpg-workaround</link><description>&lt;p&gt;My Emacs (v29.1) was hanging when saving changes to .authinfo.gpg. Turns out, I ran into a &lt;a href="http://git.savannah.gnu.org/cgit/emacs.git/commit/etc/PROBLEMS?id=1b9812af80b6ceec8418636dbf84c0fbcd3ab694"&gt;known issue&lt;/a&gt; with a workaround. Downgrading gnupgp to a version older than 2.4.1 sorts things out.&lt;/p&gt;
&lt;p&gt;I'm on macOS. Downgraded by downloading the 2.4.0 Homebrew formula at &lt;a href="https://raw.githubusercontent.com/Homebrew/homebrew-core/59edfe598541186430d49cc34f42671e849e2fc9/Formula/gnupg.rb"&gt;https://raw.githubusercontent.com/Homebrew/homebrew-core/59edfe598541186430d49cc34f42671e849e2fc9/Formula/gnupg.rb&lt;/a&gt; and installing with:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.bash"&gt;brew unlink gnupg
brew install ~/Downloads/gnupg.rb
&lt;/code&gt;&lt;/pre&gt;</description><author>xenodium.com @alvaro</author><pubDate>Sat, 16 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/emacs-hangs-saving-authinfogpg-workaround</guid></item><item><title>Retentive Networks and RWKV</title><link>https://ben.bolte.cc/posts/2023-09-16-retnet</link><description>A short, hand-wavy explainer for the mathematical intuition behind faster attention mechanisms.</description><author>Ben Bolte</author><pubDate>Sat, 16 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ben.bolte.cc/posts/2023-09-16-retnet</guid></item><item><title>Music that gets me into flow state while coding</title><link>https://gourav.io/blog/work-music</link><description>Music that gets me into flow state while coding</description><author>Gourav Goyal</author><pubDate>Sat, 16 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://gourav.io/blog/work-music</guid></item><item><title>Dear Child: Season 1</title><link>https://olshansky.info/tv/dear_child_season_1/</link><description>Olshansky's review of Dear Child: Season 1</description><author>🦉 olshansky 🦁</author><pubDate>Sat, 16 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/tv/dear_child_season_1/</guid></item><item><title>Munnar, Kerala</title><link>https://www.cpuonfire.com/photos/munnar-pond/</link><description>Munnar is a hill station in Kerala famous for its Tea plantations</description><author>cpuonfire</author><pubDate>Sat, 16 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.cpuonfire.com/photos/munnar-pond/</guid></item><item><title>Random sign</title><link>https://www.cpuonfire.com/photos/restricted-entry/</link><description>Found this sign at the entrance of a police establishment.</description><author>cpuonfire</author><pubDate>Sat, 16 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.cpuonfire.com/photos/restricted-entry/</guid></item><item><title>King of the Bunny Hill</title><link>https://taylor.town/king-of-the-bunny-hill</link><description>Some say he hasn't fallen once in 28 years.</description><author>taylor.town</author><pubDate>Sat, 16 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/king-of-the-bunny-hill</guid></item><item><title>P**fectionism Isn't Your Problem</title><link>https://taylor.town/perfectionism-isnt-your-problem</link><description>P**fectionists have tastes that exceed their skills. You can't control your taste, but you can control your skill-level.</description><author>taylor.town</author><pubDate>Sat, 16 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/perfectionism-isnt-your-problem</guid></item><item><title>New and improved way to review GitHub pull requests for snaps</title><link>https://kyrofa.com/posts/new-and-improved-way-to-review-github-pull-requests-for-snaps/</link><description>Reviewing pull requests for snaps has been pretty terrible ever since snaps were introduced. There are a few very simple reasons:
 Building snaps locally in order to review is too much work. This is a fine place to start, but it doesn&amp;rsquo;t scale, especially if it takes any amount of time to build the snap in question (mine takes over an hour). Most CI engines use Docker. You can build snaps in docker with hacks and tweaks that sort of work sometimes, but it&amp;rsquo;s not a supported approach, and it&amp;rsquo;ll just randomly break next week.</description><author>kyrofa's blog</author><pubDate>Sat, 16 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://kyrofa.com/posts/new-and-improved-way-to-review-github-pull-requests-for-snaps/</guid></item><item><title>How I use Obsidian</title><link>https://stephango.com/vault</link><description>&lt;p&gt;I use &lt;a class="internal-link" href="/obsidian"&gt;Obsidian&lt;/a&gt; to think, take notes, write essays, and publish this site. This is my bottom-up approach to note-taking and organizing things I am interested in. It embraces chaos and laziness to create emergent structure.&lt;/p&gt;

&lt;p&gt;In Obsidian, a “vault” is simply a folder of files. This is important because it adheres to my &lt;a class="internal-link" href="/file-over-app"&gt;file over app&lt;/a&gt; philosophy. If you want to create digital artifacts that last, they must be files you can control, in formats that are easy to retrieve and read. Obsidian gives you that freedom.&lt;/p&gt;

&lt;p&gt;The following is in no way dogmatic, just one example of how you can use Obsidian. Take the parts you like.&lt;/p&gt;

&lt;h2 id="vault-template"&gt;Vault template&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href="https://github.com/kepano/kepano-obsidian/archive/refs/heads/main.zip"&gt;Download my vault&lt;/a&gt; or clone it from &lt;a href="https://github.com/kepano/kepano-obsidian"&gt;the Github repo&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Unzip the &lt;code class="language-plaintext highlighter-rouge"&gt;.zip&lt;/code&gt; file to a folder of your choosing.&lt;/li&gt;
  &lt;li&gt;In Obsidian open the folder as a vault.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id="theme-and-related-tools"&gt;Theme and related tools&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;My theme &lt;a class="internal-link" href="/minimal"&gt;Minimal&lt;/a&gt; with the &lt;a class="internal-link" href="/flexoki"&gt;Flexoki&lt;/a&gt; color scheme.&lt;/li&gt;
  &lt;li&gt;&lt;a class="internal-link" href="/obsidian-web-clipper"&gt;Obsidian Web Clipper&lt;/a&gt; to save articles and pages from the web, see my &lt;a href="https://github.com/kepano/clipper-templates"&gt;clipper templates&lt;/a&gt; for specific sites I clip from.&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://obsidian.md/sync"&gt;Obsidian Sync&lt;/a&gt; to sync notes between my desktop, phone and tablet.&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://help.obsidian.md/bases"&gt;Obsidian Bases&lt;/a&gt; to view notes by category.&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://help.obsidian.md/bases/views/map"&gt;Obsidian Maps&lt;/a&gt; for maps used in some of my templates.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="personal-rules"&gt;Personal rules&lt;/h2&gt;

&lt;p&gt;Rules I follow in my personal vault:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Avoid splitting content into multiple vaults.&lt;/li&gt;
  &lt;li&gt;Avoid folders for organization.&lt;/li&gt;
  &lt;li&gt;Avoid non-standard Markdown.&lt;/li&gt;
  &lt;li&gt;Always pluralize categories and tags.&lt;/li&gt;
  &lt;li&gt;Use internal links profusely.&lt;/li&gt;
  &lt;li&gt;Use &lt;code class="language-plaintext highlighter-rouge"&gt;YYYY-MM-DD&lt;/code&gt; dates everywhere.&lt;/li&gt;
  &lt;li&gt;Use the 7-point scale for ratings.&lt;/li&gt;
  &lt;li&gt;Keep &lt;a class="internal-link" href="/todos"&gt;a single to-do list&lt;/a&gt; per week.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Having a &lt;a class="internal-link" href="/style"&gt;consistent style&lt;/a&gt; collapses hundreds of future decisions into one, and gives me focus. For example, I always pluralize tags so I never have to wonder what to name new tags. Choose rules that feel comfortable to you and write them down. Make your own style guide. You can always change your rules later.&lt;/p&gt;

&lt;h2 id="folders-and-organization"&gt;Folders and organization&lt;/h2&gt;

&lt;p&gt;I use very few folders. I avoid folders because many of my entries belong to more than one area of thought. My system is oriented towards speed and laziness. I don’t want the overhead of having to consider where something should go.&lt;/p&gt;

&lt;p&gt;I do not use nested sub-folders. I do not use the file explorer much for navigation. I mostly navigate using the quick switcher, backlinks, or links within a note.&lt;/p&gt;

&lt;p&gt;My notes are primarily organized using the &lt;code class="language-plaintext highlighter-rouge"&gt;categories&lt;/code&gt; property. Categories display an overview of related notes, using the &lt;a href="https://help.obsidian.md/bases"&gt;bases&lt;/a&gt; feature in Obsidian.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Most of my notes are in the root of the vault&lt;/strong&gt;, not a folder. This where I write about my personal world: journal entries, essays, &lt;a class="internal-link" href="/evergreen-notes"&gt;evergreen&lt;/a&gt; notes, and other personal notes. If a note is in the root, I know it’s something I wrote, or relates directly to me.&lt;/p&gt;

&lt;p&gt;Two reference folders I use:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;References&lt;/strong&gt; where I write about things that exist outside my world. Books, movies, places, people, podcasts, etc. Always named using the title e.g. &lt;code class="language-plaintext highlighter-rouge"&gt;Book title.md&lt;/code&gt; or &lt;code class="language-plaintext highlighter-rouge"&gt;Movie title.md&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Clippings&lt;/strong&gt; where I save things other people wrote, mostly essays and articles.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three admin folders exist so that their contents don’t show up in the file navigation:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Attachments&lt;/strong&gt; for images, audio, videos, PDFs, etc.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Daily&lt;/strong&gt; for my daily notes, all named &lt;code class="language-plaintext highlighter-rouge"&gt;YYYY-MM-DD.md&lt;/code&gt;. I do not write anything in daily notes, they exist solely to be linked to from other entries.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Templates&lt;/strong&gt; for templates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two folders are present in the downloadable version of my vault for the sake of clarity. In my personal vault, these notes would be in the root, not a folder.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Categories&lt;/strong&gt; contains top-level overviews of notes per category (e.g. Books, Movies, Podcasts, etc).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Notes&lt;/strong&gt; contains example notes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="links"&gt;Links&lt;/h2&gt;

&lt;p&gt;I use internal links profusely throughout my notes. I try to always link the first mention of something. My journal entries are often a stream of consciousness cataloging recent events, finding connections between things. Often the link is &lt;em&gt;unresolved&lt;/em&gt;, meaning that the note for that link isn’t created yet. Unresolved links are important because they are breadcrumbs for future connections between things.&lt;/p&gt;

&lt;p&gt;A journal entry in the &lt;strong&gt;root&lt;/strong&gt; of my vault might look something like this:&lt;/p&gt;

&lt;div class="language-plaintext highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;I went to see the movie [[Perfect Days]] with [[Aisha]] at [[Vidiots]] and had Filipino food at [[Little Ongpin]]. I loved this quote from Perfect Days: [[Next time is next time, now is now]]. It reminds me of the essay ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The movie, movie theater, and restaurant each link to entries in my &lt;strong&gt;References&lt;/strong&gt; folder. In these reference notes I capture properties, my rating, and thoughts about that thing. I use &lt;a class="internal-link" href="/obsidian-web-clipper"&gt;Web Clipper&lt;/a&gt; to help populate properties from databases like IMDB. The quote was meaningful to me, so it became an &lt;a class="internal-link" href="/evergreen-notes"&gt;evergreen note&lt;/a&gt; in my root folder. The essay I mention is in my &lt;strong&gt;Clippings&lt;/strong&gt; folder, because I didn’t write it myself.&lt;/p&gt;

&lt;p&gt;This heavy linking style becomes more useful as time goes on, because I can trace how ideas emerged, and the branching paths these ideas created.&lt;/p&gt;

&lt;h2 id="fractal-journaling-and-random-revisit"&gt;Fractal journaling and random revisit&lt;/h2&gt;

&lt;p&gt;Fractal journaling and randomization are how I tame the wilderness that a knowledge base can grow into.&lt;/p&gt;

&lt;p&gt;Throughout the day I use Obsidian’s &lt;em&gt;unique note&lt;/em&gt; hotkey to write individual thoughts as they come up. This shortcut automatically creates a note with the prefix &lt;code class="language-plaintext highlighter-rouge"&gt;YYYY-MM-DD HHmm&lt;/code&gt; to which I may add a title that describes the idea.&lt;/p&gt;

&lt;p&gt;Every few days I review these journal fragments and compile the salient thoughts. I then review those reviews monthly, and review the monthly reviews yearly (using &lt;a class="internal-link" href="/40-questions"&gt;this template&lt;/a&gt;). The result is a fractal web of my life that I can zoom in and out of at varying degrees of detail. I can trace back where individual thoughts came from, and how they bubbled up into bigger themes.&lt;/p&gt;

&lt;p&gt;Every few months I set aside time for a “random revisit”. I use the &lt;em&gt;random note&lt;/em&gt; hotkey to quickly travel randomly through my vault. I often use the local graph at shallow depth to see related notes. This helps me revisit old ideas, create missing links, and find inspiration in past thoughts. It’s also an opportunity to do maintenance, like fix formatting based on new rules in my personal style guide.&lt;/p&gt;

&lt;p&gt;People have asked me if this could be automated with language models but I do not care to do so. I enjoy this process. Doing this maintenance helps me understand my own patterns. &lt;a class="internal-link" href="/understand"&gt;Don’t delegate understanding&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="properties-and-templates"&gt;Properties and templates&lt;/h2&gt;

&lt;p&gt;Almost every note I create starts from a &lt;a href="https://github.com/kepano/kepano-obsidian/tree/main/Templates"&gt;template&lt;/a&gt;. I use templates heavily because they allow me to lazily add information that will help me find the note later. I have a template for every category with &lt;a href="https://help.obsidian.md/properties"&gt;properties&lt;/a&gt; at the top, to capture data such as:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Dates&lt;/strong&gt; — created, start, end, published&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;People&lt;/strong&gt; — author, director, artist, cast, host, guests&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Themes&lt;/strong&gt; — grouping by genre, type, topic, related notes&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Locations&lt;/strong&gt; — neighborhood, city, coordinates&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Ratings&lt;/strong&gt; — more on this below&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A few rules I follow for properties:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Property names and values should aim to be reusable across categories. This allows me to find things across categories, e.g. &lt;code class="language-plaintext highlighter-rouge"&gt;genre&lt;/code&gt; is shared across all media types, which means I can see an archive of &lt;em&gt;Sci-fi&lt;/em&gt; books, movies and shows in one place.&lt;/li&gt;
  &lt;li&gt;Templates should aim to be composable, e.g. &lt;em&gt;Person&lt;/em&gt; and &lt;em&gt;Author&lt;/em&gt; are two different templates that can be added to the same note.&lt;/li&gt;
  &lt;li&gt;Short property names are faster to type, e.g. &lt;code class="language-plaintext highlighter-rouge"&gt;start&lt;/code&gt; instead of &lt;code class="language-plaintext highlighter-rouge"&gt;start‑date&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Default to &lt;code class="language-plaintext highlighter-rouge"&gt;list&lt;/code&gt; type properties instead of &lt;code class="language-plaintext highlighter-rouge"&gt;text&lt;/code&gt; if there is any chance it might contain more than one link or value in the future.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://github.com/kepano/kepano-obsidian/blob/main/.obsidian/types.json"&gt;.obsidian/types.json&lt;/a&gt; file lists which properties are assigned to which types (i.e. &lt;code class="language-plaintext highlighter-rouge"&gt;date&lt;/code&gt;, &lt;code class="language-plaintext highlighter-rouge"&gt;number&lt;/code&gt;, &lt;code class="language-plaintext highlighter-rouge"&gt;text&lt;/code&gt;, etc).&lt;/p&gt;

&lt;h2 id="rating-system"&gt;Rating system&lt;/h2&gt;

&lt;p&gt;Anything with a &lt;code class="language-plaintext highlighter-rouge"&gt;rating&lt;/code&gt; uses an integer from 1 to 7:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;7 — &lt;strong&gt;Perfect&lt;/strong&gt;, must try, life-changing, go out of your way to seek this out&lt;/li&gt;
  &lt;li&gt;6 — &lt;strong&gt;Excellent&lt;/strong&gt;, worth repeating&lt;/li&gt;
  &lt;li&gt;5 — &lt;strong&gt;Good&lt;/strong&gt;, don’t go out of your way, but enjoyable&lt;/li&gt;
  &lt;li&gt;4 — &lt;strong&gt;Passable&lt;/strong&gt;, works in a pinch&lt;/li&gt;
  &lt;li&gt;3 — &lt;strong&gt;Bad&lt;/strong&gt;, don’t do this if you can&lt;/li&gt;
  &lt;li&gt;2 — &lt;strong&gt;Atrocious&lt;/strong&gt;, actively avoid, repulsive&lt;/li&gt;
  &lt;li&gt;1 — &lt;strong&gt;Evil&lt;/strong&gt;, life-changing in a bad way&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why this scale? I like rating out of 7 better than 4 or 5 because I need more granularity at the top, for the good experiences, and 10 is too granular.&lt;/p&gt;

&lt;h2 id="publishing-to-the-web"&gt;Publishing to the web&lt;/h2&gt;

&lt;p&gt;This site is written, edited, and published directly from Obsidian. To do this, I break one of my rules listed above — I have a separate vault for my site. I use a &lt;em&gt;static site generator&lt;/em&gt; called &lt;a href="https://jekyllrb.com/"&gt;Jekyll&lt;/a&gt; to automatically compile my notes into a website and convert them from Markdown to HTML.&lt;/p&gt;

&lt;p&gt;My publishing flow is easy to use, but a bit technical to set up. This is because I like to have full control over every aspect of my site’s layout. If you don’t need full control you might consider &lt;a href="https://obsidian.md/publish"&gt;Obsidian Publish&lt;/a&gt; which is more user-friendly, and what I use for my &lt;a href="https://minimal.guide/publish/download"&gt;Minimal documentation site&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For this site, I push notes from Obsidian to a GitHub repo using the &lt;a href="https://obsidian.md/plugins?id=obsidian-git"&gt;Obsidian Git&lt;/a&gt; plugin. The notes are then automatically compiled using &lt;a href="https://jekyllrb.com/"&gt;Jekyll&lt;/a&gt; with my web host &lt;a href="https://www.netlify.com/"&gt;Netlify&lt;/a&gt;. I also use my &lt;a class="internal-link" href="/permalink-opener"&gt;Permalink Opener&lt;/a&gt; plugin to quickly open notes in the browser so I can compare the draft and live versions.&lt;/p&gt;

&lt;p&gt;The color palette is &lt;a class="internal-link" href="/flexoki"&gt;Flexoki&lt;/a&gt;, which I created for this site. My Jekyll template is not public, but you can get similar results from &lt;a href="https://github.com/maximevaillancourt/digital-garden-jekyll-template"&gt;this template&lt;/a&gt;  by Maxime Vaillancourt. There are also many alternatives to Jekyll you can use to compile your site such as &lt;a href="https://quartz.jzhao.xyz/"&gt;Quartz&lt;/a&gt;, &lt;a href="https://astro.build/"&gt;Astro&lt;/a&gt;, &lt;a href="https://www.11ty.dev/"&gt;Eleventy&lt;/a&gt;, and &lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="related-writing"&gt;Related writing&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a class="internal-link" href="/file-over-app"&gt;File over app&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a class="internal-link" href="/concise"&gt;Concise explanations accelerate progress&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a class="internal-link" href="/evergreen-notes"&gt;Evergreen notes turn ideas into objects that you can manipulate&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a class="internal-link" href="/40-questions"&gt;40 questions to ask yourself every year&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a class="internal-link" href="/40-questions-decade"&gt;40 questions to ask yourself every decade&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a class="internal-link" href="/todos"&gt;How I do my to-dos&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Steph Ango</author><pubDate>Sat, 16 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://stephango.com/vault</guid></item><item><title>Reading files on Vercel during Next.js ISR</title><link>https://francoisbest.com/posts/2023/reading-files-on-vercel-during-nextjs-isr?utm_source=rss</link><description>A little hack to let the Next.js file tracer pick up relative file paths.</description><author>Articles by François Best</author><pubDate>Sat, 16 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://francoisbest.com/posts/2023/reading-files-on-vercel-during-nextjs-isr?utm_source=rss</guid></item><item><title>World of Troubles</title><link>https://foreverliketh.is/blog/world-of-troubles/</link><description>&lt;details&gt;
Image Details &amp; Source
&lt;p&gt;Frame from &lt;a href="https://en.wikipedia.org/wiki/Fire_Emblem_Awakening"&gt;Fire Emblem Awakening&lt;/a&gt; cutscene: &lt;a href="https://www.youtube.com/watch?v=-dEaQIjJL7Q&amp;amp;t=651s"&gt;&amp;ldquo;Marth No More&amp;rdquo;&lt;/a&gt; by &lt;a href="https://www.studioanima.co.jp/works/project-100/"&gt;ANIMA Inc.&lt;/a&gt;*&lt;/p&gt;
&lt;p&gt;The focus is on &lt;a href="https://en.wikipedia.org/wiki/Lucina_(Fire_Emblem)"&gt;Lucina&amp;rsquo;s&lt;/a&gt; left eye. On how it bears the &lt;a href="https://fireemblem.fandom.com/wiki/Exalt"&gt;&amp;ldquo;Brand of the Exalt&amp;rdquo;&lt;/a&gt;, and on how a tear is beginning to form.&lt;/p&gt;
&lt;/details&gt;
&lt;hr /&gt;
&lt;p&gt;This page was last modified: &lt;strong&gt;September 17th, 2023&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h5 id="2020"&gt;20/20 &lt;a class="anchor" href="#2020"&gt;#&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;I was thinking about my father today, visitor. It wasn&amp;rsquo;t my intention but I began to recall moments (and ways) &lt;em&gt;&lt;strong&gt;that I feel&lt;/strong&gt;&lt;/em&gt; he let me down. I won&amp;rsquo;t waste your time with them; I&amp;rsquo;ve no doubt there are a countless number of people that could unquestioningly prove to you that their parent(s) failed them.&lt;/p&gt;
&lt;p&gt;In my opinion, I was blessed with incredible parents. To nitpick, as I let my mind do today, is unfair; possibly to an insulting degree. Mind you, I&amp;rsquo;m not taking any of it back, but know that, if I could, I&amp;rsquo;d pick them over and over again, every single life.&lt;/p&gt;
&lt;p&gt;Still, one can&amp;rsquo;t help but wonder: &amp;ldquo;What If&amp;hellip;?&amp;rdquo; Or at least &lt;em&gt;&lt;strong&gt;I&lt;/strong&gt;&lt;/em&gt; can&amp;rsquo;t help it. And that led me to reason the following, reader: If even a &amp;ldquo;grateful&amp;rdquo; child (like me) can feel disappointed in their parent(s), what hope do I have as a father?&lt;/p&gt;
&lt;h5 id="absolutely-none"&gt;Absolutely None &lt;a class="anchor" href="#absolutely-none"&gt;#&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;So, rather than a post of me b—ing and moaning about my daddy issues, I thought it&amp;rsquo;d be more interesting to kind-of turn that on its head. I.e. this write-up is now a prediction on how &lt;em&gt;&lt;strong&gt;exactly&lt;/strong&gt;&lt;/em&gt; I f— up / screw over my imaginary, future progeny:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;I&amp;rsquo;m sorry I raised you in the city. You should&amp;rsquo;ve had space to play, and nature to explore.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I&amp;rsquo;m sorry your mother and I divorced; there was no way I got it right.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I&amp;rsquo;m sorry I didn&amp;rsquo;t make you independent or responsible enough for adulthood.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I&amp;rsquo;m sorry you&amp;rsquo;re stupid. My b, yo.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I&amp;rsquo;m sorry I wasn&amp;rsquo;t serious enough. I tend to consider a lot of things not very important, so I probably let too much slide. I probably justified it as &amp;ldquo;choosing my battles&amp;rdquo;, but I bet I was just lazy and instead made you feel like things that should&amp;rsquo;ve mattered, didn&amp;rsquo;t.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I&amp;rsquo;m sorry I didn&amp;rsquo;t drink or do drugs, and how it 100% guaranteed that you would. I&amp;rsquo;m sorry that I failed you in your fight against your addiction(s). Please find help and/or Jesus.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I&amp;rsquo;m sorry I discouraged your nerdy tendencies. I thought you&amp;rsquo;d be better off touching as much grass as possible. I should&amp;rsquo;ve taught you how to find balance, rather than suppress potentially problematic parts of yourself.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sorry I made you too distrustful and wary of women, Timmy. Rather than scare you, I should&amp;rsquo;ve helped you become a man who wouldn&amp;rsquo;t consider them such a big deal. Please disregard if gay. Also: the opposite goes for you, Crystal.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I&amp;rsquo;m sorry I didn&amp;rsquo;t have more answers.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id="half-full"&gt;Half Full &lt;a class="anchor" href="#half-full"&gt;#&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;Was this too sad, or weird? Oh well. 🤷‍♀️ Too late now. It&amp;rsquo;d be fun to find out how &amp;ldquo;on the money&amp;rdquo; I am. Honestly, imo, I definitely could&amp;rsquo;ve done you worse; quit your wailing! Also I am allowed to tweak this right up until you (if ever) become reality.&lt;/p&gt;
&lt;p&gt;You know&amp;hellip; We are so much more than our parent(s). At the end of the day, we turn out how we do in spite of them, for better and for worse&amp;hellip;&lt;/p&gt;
&lt;p&gt;I believe in ya, kid. You got this.&lt;/p&gt;
&lt;hr /&gt;
&lt;details&gt;
Written for Chrom
&amp;ldquo;You deserved better from me than one sword and a world of troubles&amp;hellip;&amp;rdquo;&lt;/details&gt;
&lt;p&gt;Contributors: Mom and Dad, &lt;a href="https://aralsheart.ichi.city/"&gt;Aral&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This blog post is part of Agora Road Travelogue - Sep [&lt;a href="https://forum.agoraroad.com/index.php?threads/agora-road-travelouge-september-blogging.6061/"&gt;#1&lt;/a&gt; &amp;amp; #2]. A project attempting to encourage individuals to both write and exercise ownership over their content.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id="comments-section"&gt;Comments Section &lt;a class="anchor" href="#comments-section"&gt;#&lt;/a&gt;&lt;/h2&gt;

&lt;div id="fastcomments-widget"&gt;&lt;/div&gt;

&lt;details&gt;
IndieWeb h-entry
&lt;article class="h-entry"&gt;
&lt;h1 class="p-name"&gt;World of Troubles&lt;/h1&gt;
&lt;p&gt;Published by &lt;a class="p-author h-card" href="https://foreverliketh.is/"&gt;foreverliketh.is&lt;/a&gt;
on &lt;time class="dt-published" datetime="2023-09-16 10:00:00"&gt;16&lt;sup&gt;th&lt;/sup&gt; September 2023&lt;/time&gt;&lt;/p&gt;
&lt;p class="p-summary"&gt;+1 Sword&lt;/p&gt;
&lt;div class="e-content"&gt;
&lt;p&gt;20/20&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;a class="u-url" href="https://foreverliketh.is/blog/world-of-troubles/"&gt;World of Troubles&lt;/a&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;a class="u-syndication" href="https://forum.agoraroad.com/index.php?threads/agora-road-travelouge-september-blogging.6061/post-87275" rel="syndication"&gt;Agora Road&lt;/a&gt; |
&lt;!--&lt;a rel="syndication" class="u-syndication" href=""&gt;basement community&lt;/a&gt; | --&gt;
&lt;a class="u-syndication" href="https://indieweb.social/@accordionpolar/111075266468764922" rel="syndication"&gt;Mastodon&lt;/a&gt;
&lt;/div&gt;
&lt;a class="p-category" href="https://foreverliketh.is/categories/psychology/"&gt;Psychology&lt;/a&gt;
&lt;/article&gt;
&lt;/details&gt;</description><author>foreverliketh.is</author><pubDate>Sat, 16 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://foreverliketh.is/blog/world-of-troubles/</guid></item><item><title>Chart of Accounts for Startups and SaaS Companies</title><link>https://neil.computer/notes/chart-of-accounts-for-startups-and-saas-companies/</link><description>&lt;figure class="kg-card kg-image-card"&gt;&lt;img alt="alt" class="kg-image" height="1200" src="https://neil.computer/content/images/2023/09/1522px-General_store_interior_Alabama_USA-1.jpg" width="1522" /&gt;&lt;/figure&gt;&lt;p&gt;Accounting is fundamental to starting a business. You need to have a basic understanding of accounting principles and essential bookkeeping. I had to learn it. There was no choice. For filing taxes, your CPA is going to ask you for an Income Statement (also known as P/L statement). If&lt;/p&gt;</description><author>Neil Panchal</author><pubDate>Sat, 16 Sep 2023 02:55:59 GMT</pubDate><guid isPermaLink="true">https://neil.computer/notes/chart-of-accounts-for-startups-and-saas-companies/</guid></item><item><title>Steam engine or bridge?</title><link>http://blog.onepatchdown.net/2023/09/15/steam-engine-bridge/</link><description>&lt;p&gt;When looking at the bigger picture of writing and maintaining complex software systems, I look at it and ask:  have I built the software-equivalent of a bridge, or a steam engine. While a bridge needs mainteance and inspection, after it’s built, it largely just sits there. You spend a lot of time up-front, designing and engineering it before even a single brick is laid.&lt;/p&gt;

&lt;p&gt;A steam engine on the other hand, only just sits there if you don’t want it to do what it was designed to do - be an engine. It needs a team of people working tirelessly round the clock babysitting it to cajole it into performing.&lt;/p&gt;

&lt;p&gt;‘ls’ is a bridge piece of software. It basically sits there. But SaaS companies’ backends are a complex steam engine piece of software, which is why you need SREs.&lt;/p&gt;</description><author>Blogity blog blog. Journal</author><pubDate>Fri, 15 Sep 2023 23:23:00 GMT</pubDate><guid isPermaLink="true">http://blog.onepatchdown.net/2023/09/15/steam-engine-bridge/</guid></item><item><title>Project SparkleTilt: My Longhorn LED Level PCB</title><link>https://karmanyaah.malhotra.cc/projects/2023/09/sparkletilt/</link><description>I made a Longhorn shaped level with Addressable RGB LEDs (Neopixels) and it’s also an Arduino!!!!</description><author>Karmanyaah Malhotra</author><pubDate>Fri, 15 Sep 2023 13:57:53 GMT</pubDate><guid isPermaLink="true">https://karmanyaah.malhotra.cc/projects/2023/09/sparkletilt/</guid></item><item><title>AirGapped TOTP</title><link>https://karmanyaah.malhotra.cc/projects/2023/08/totpv2/</link><description>Isn’t keeping your 2FA codes connected to the internet kinda insecure? In today’s episode of lets overcomplicate established procedures for marginal security improvements, I made an e-ink based, battery operated TOTP device. It’s a dedicated device to do TOTP, so instead of having to store them on your phone (where they can be remotely attacked), you store them in your bag (where they can be stolen 😄). However, since OTPs are the “something you have”, that’s alright, because random theives won’t have your passwords.</description><author>Karmanyaah Malhotra</author><pubDate>Fri, 15 Sep 2023 13:57:53 GMT</pubDate><guid isPermaLink="true">https://karmanyaah.malhotra.cc/projects/2023/08/totpv2/</guid></item><item><title>A Bit of SaaS Weekly: Find the Fit</title><link>https://ethanmick.com/a-bit-of-saas-weekly-find-the-fit/</link><description>&lt;p&gt;This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by &lt;a href="https://ethanmick.com/"&gt;Ethan Mick&lt;/a&gt;.&lt;/p&gt;&lt;blockquote&gt;The biggest productivity hack is simply finishing what you started. Once you train yourself to be ruthless about that, you are quite cautious about flippantly starting new things. Only start&lt;/blockquote&gt;</description><author>Build SaaS with Ethan</author><pubDate>Fri, 15 Sep 2023 13:00:12 GMT</pubDate><guid isPermaLink="true">https://ethanmick.com/a-bit-of-saas-weekly-find-the-fit/</guid></item><item><title>Audio Description Resources</title><link>https://brett.coulstock.id.au/audio-description-resources.html</link><description>A selection of interesting or useful links relating to audio-description.</description><author>Brett Coulstock - Writer</author><pubDate>Fri, 15 Sep 2023 12:00:00 GMT</pubDate><guid isPermaLink="true">https://brett.coulstock.id.au/audio-description-resources.html</guid></item><item><title>Cosmos DB API for MongoDB dump and restore</title><link>https://sergeykibish.com/blog/cosmos-db-api-for-mongodb-dump-and-restore/</link><description>Ever wondered if you can perform an offline migration from one Cosmos DB API for MongoDB to another using native MongoDB database tools? It's not only possible, but I'll walk you through the process step by step. Let's get started.</description><author>Sergey's Blog</author><pubDate>Fri, 15 Sep 2023 10:00:00 GMT</pubDate><guid isPermaLink="true">https://sergeykibish.com/blog/cosmos-db-api-for-mongodb-dump-and-restore/</guid></item><item><title>Redact that buffer</title><link>https://xenodium.com/redact-that-buffer</link><description>&lt;p&gt;As I was getting ready to take an Emacs screenshot in the &lt;a href="https://xenodium.com/emacs-send-to-kindle/"&gt;previous post&lt;/a&gt;, I figured I may want to redact email addresses before moving forward. I had a quick look for existing options and found &lt;a href="https://github.com/bkaestner/redacted.el"&gt;redacted.el&lt;/a&gt;, built-in &lt;code&gt;toggle-rot13-mode&lt;/code&gt;, and &lt;a href="https://github.com/alphapapa/unpackaged.el#obfuscate-buffer-text-with-lorem-ipsum-words"&gt;unpackaged/lorem-ipsum-overlay&lt;/a&gt;. All great options. I wanted a solution I could feed a single regular expression to obscure matches. I also wanted toggling capabilities, so I had a quick go at it…&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/redact-that-buffer/redact-regexp.gif" /&gt;&lt;/p&gt;
&lt;p&gt;I also wanted the ability to redact the entire buffer content, so feeding a space to the regexp query also translates to &lt;code&gt;[[:graph:]]&lt;/code&gt;, effectively redacting all visible characters.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/redact-that-buffer/redact-all.gif" /&gt;&lt;/p&gt;
&lt;p&gt;The solution is overlay-based, ensuring the buffer content remains unchanged. The function may have its own rough edges, yet it certainly scratched the itch for the current need. I'll leave ya with the snippet.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(defun ar/toggle-redact-buffer ()
  &amp;quot;Redact buffer content matching regexp. A space redacts all.&amp;quot;
  (interactive)
  (let* ((redacted)
         (regexp (string-trim (read-regexp &amp;quot;Redact regexp&amp;quot; 'regexp-history-last)))
         (matches (let ((results '()))
                    (when (string-empty-p regexp)
                      (setq regexp &amp;quot;[[:graph:]]&amp;quot;)
                      (setq regexp-history-last regexp)
                      (add-to-history 'regexp-history regexp))
                    (save-excursion
                      (goto-char (point-min))
                      (while (re-search-forward regexp nil t)
                        (push (cons (match-beginning 0) (match-end 0)) results)))
                    (nreverse results))))
    (mapc (lambda (match)
            (dolist (overlay (overlays-in (car match) (cdr match)))
              (setq redacted t)
              (delete-overlay overlay))
            (unless redacted
              (overlay-put (make-overlay (car match) (cdr match))
                           'display (make-string (- (cdr match) (car match)) ?x))))
          matches)))
&lt;/code&gt;&lt;/pre&gt;</description><author>xenodium.com @alvaro</author><pubDate>Fri, 15 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/redact-that-buffer</guid></item><item><title>Send note to Kindle</title><link>https://xenodium.com/emacs-send-to-kindle</link><description>&lt;p&gt;While on Mastodon, I spotted &lt;a href="https://indieweb.social/@summeremacs@fashionsocial.host"&gt;@summeremacs&lt;/a&gt; looking into &lt;a href="https://indieweb.social/@summeremacs@fashionsocial.host/111058226788825431"&gt;sending Emacs text selections to a Kindle via email&lt;/a&gt;. This sparked my interest as I previously looked into &lt;a href="https://xenodium.com/emailing-pdfs-to-kindle-from-mu4e/"&gt;sending pdfs to my Kindle&lt;/a&gt; via &lt;a href="https://github.com/djcb/mu"&gt;mu4e&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Kindle offers a neat service where you can email a file to your &lt;code&gt;@kindle.com&lt;/code&gt; address and it automatically shows up in your Kindle library.&lt;/p&gt;
&lt;p&gt;I already do email from my beloved editor, and like most Emacs things, it's powered by &lt;a href="https://en.wikipedia.org/wiki/Emacs_Lisp"&gt;elisp&lt;/a&gt;. In other words, it's basically up for grabs if you'd like to glue it to anything else, so I did…&lt;/p&gt;
&lt;p&gt;I can now select a region and invoke &lt;code&gt;M-x send-to-kindle-as-txt&lt;/code&gt; to send it over to my Kindle.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/emacs-send-to-kindle/send-to-my-kindle.gif" /&gt;&lt;/p&gt;
&lt;p&gt;Soon enough, the note shows up on my Kindle.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/emacs-send-to-kindle/listed.png" /&gt;&lt;/p&gt;
&lt;p&gt;Opening the note reveals the same content we had previously selected and sent from our malleable editor.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/emacs-send-to-kindle/repeated.png" /&gt;&lt;/p&gt;
&lt;p&gt;While it looks kinda magical, it's fairly simple under the hood. It takes the region content, writes it to a txt file, creates an email message buffer attaching the file, and finally sends via &lt;code&gt;message-send-and-exit&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If &lt;code&gt;M-x send-to-kindle-as-txt&lt;/code&gt; is invoked with a &lt;code&gt;C-u&lt;/code&gt; prefix, you get to inspect the message buffer right before sending via &lt;code&gt;C-c C-c&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/emacs-send-to-kindle/email.png" /&gt;&lt;/p&gt;
&lt;p&gt;Here's the full snippet.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(defcustom send-to-kindle-from-email
  nil
  &amp;quot;Your own email address to send from via mu4e.&amp;quot;
  :type 'string
  :group 'send-to-kindle)

(defcustom send-to-kindle-to-email
  nil
  &amp;quot;Your Kindle email address to send pdf to.&amp;quot;
  :type 'string
  :group 'send-to-kindle)

(defun send-to-kindle-as-txt (review)
  (interactive &amp;quot;P&amp;quot;)
  (unless send-to-kindle-from-email
    (setq send-to-kindle-from-email
          (read-string &amp;quot;From email address: &amp;quot;)))
  (unless send-to-kindle-to-email
    (setq send-to-kindle-to-email
          (read-string &amp;quot;To email address: &amp;quot;)))
  (let* ((content (string-trim (if (region-active-p)
                                   (buffer-substring (region-beginning) (region-end))
                                 (buffer-string))))
         (note-name (let ((name (string-trim (read-string &amp;quot;Note name: &amp;quot;))))
                      (if (string-empty-p name)
                          (nth
                           0 (string-split
                              (substring content 0 (min 40 (length content))) &amp;quot;\n&amp;quot;))
                        name)))
         (path (concat (temporary-file-directory) note-name))
         (txt (concat path &amp;quot;.txt&amp;quot;))
         (buffer (get-buffer-create (generate-new-buffer-name &amp;quot;*Email txt*&amp;quot;))))
    (with-temp-buffer
      (insert content)
      (write-file txt))
    (with-current-buffer buffer
      (erase-buffer)
      ;; Disable hooks
      (let ((message-mode-hook nil))
        (message-mode))
      (insert
       (format
        &amp;quot;From: %s
To: %s
Subject: %s
--text follows this line--
&amp;lt;#multipart type=mixed&amp;gt;
&amp;lt;#part type=\&amp;quot;text/plain\&amp;quot; filename=\&amp;quot;%s\&amp;quot; disposition=attachment&amp;gt;
&amp;lt;#/part&amp;gt;
&amp;lt;#/multipart&amp;gt;&amp;quot;
        send-to-kindle-from-email
        send-to-kindle-to-email
        note-name txt))
      (unless review
        (message-send-and-exit)))
    (when review
      (switch-to-buffer buffer))))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By the way, and I only just learned this today… To take a screenshot on a Kindle Paperwhite, tap on these opposite corners.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/emacs-send-to-kindle/tap-screenshot.png" /&gt;&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Fri, 15 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/emacs-send-to-kindle</guid></item><item><title>Marginalia Search receives FUTO Grant</title><link>https://www.marginalia.nu/log/88-futo-grant/</link><description>&lt;p&gt;I&amp;rsquo;m happy to announce that the generous people at &lt;a href="https://futo.org/"&gt;FUTO&lt;/a&gt; have granted the project $15,000 with no strings attached to help the search engine out with some more server power.&lt;/p&gt;
&lt;p&gt;FUTO is a young Austin, TX-based organization &amp;ldquo;&lt;em&gt;dedicated to developing, both through in-house engineering and investment, technologies that frustrate centralization and industry consolidation&lt;/em&gt;&amp;rdquo;. It&amp;rsquo;s one to keep an eye on, I believe their heart is in the right place and they have every possibility of making a real difference.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Fri, 15 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/88-futo-grant/</guid></item><item><title>Writing code the linear way</title><link>https://gourav.io/blog/linear-code</link><description>When I refer to "linear code", I generally mean code that is written in a straightforward, top-down manner without nested structures, which can be more readable because it tends to follow a single, simple path of execution.</description><author>Gourav Goyal</author><pubDate>Fri, 15 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://gourav.io/blog/linear-code</guid></item><item><title>You don't need LPM Tries</title><link>https://cookie.engineer/weblog/articles/you-dont-need-lpm-tries.html</link><description>There's no need for LPM Trie data structures for network lookups, because you can use LPM hash sets. This article explains the origin of the concept, its implementation and why it's better.</description><author>Cookie Engineer's Web Log</author><pubDate>Fri, 15 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://cookie.engineer/weblog/articles/you-dont-need-lpm-tries.html</guid></item><item><title>Everything is intertwingled</title><link>https://holzer.online/weeknotes/2023/37/index.html</link><description>&lt;p&gt;
I've wanted to replace the scripts with which I create this website for quite some time now. My most important goal was to be able to build the site with a single command. Generating the RSS feed turned out to be a bit of a tyranical edge case. Within the last week I got to the point where I am satisfied enough with the result to finally ditch the two old scripts start using it for this website.
&lt;/p&gt;

&lt;p&gt;
For those who read my site via the feed, this is also the reason why some old articles might have popped in your reader up all at once. The old script was heavily depending on a certain regular structure of the directory, and I would have needed to run the rss generation script for each category and then somehow properly merge the resulting xml files, which I found to be too tedious, so the weeknote category of the site was left out (which was another driver for the rewrite).
&lt;/p&gt;

&lt;p&gt;
There is a great xkcd about &lt;a href="https://xkcd.com/1205/"&gt;how much time you may spend on automation before yielding negative returns&lt;/a&gt;. If all the tedious stuff took about 5 minutes per post and I'll write once a week, about 21 hours of programming amortize themselves over the course of five years. But even if programming a custom SSG turns out to be uneconomically from the point of timesaving, it still was fun, which is enough reason to do it. I've put both the the generator, which I named &lt;a href="https://github.com/fnh/intertwingle/"&gt;intertwingle&lt;/a&gt;, as well as the source of &lt;a href="https://github.com/fnh/holzer.online/"&gt;my website&lt;/a&gt; on github.
&lt;/p&gt;

&lt;p&gt;
On to a completely different subject. This week at work, I heard that upper management is looking into establishing tools that create metrics from our repositories and issue tracker to be reported to them. I don't know whether it is merely a conincidence that a month or so ago an article about measuring developer productivity by a big management consultancy made the rounds. Kent Becks response in two articles (&lt;a href="https://tidyfirst.substack.com/p/measuring-developer-productivity"&gt;Part 1&lt;/a&gt; and &lt;a href="https://tidyfirst.substack.com/p/measuring-developer-productivity-440"&gt;Part 2&lt;/a&gt;) is quite good. In a debate on Linkedin Kent also delivered a soundbite, that sums it up up nicely: &lt;q&gt;&lt;a href="https://www.linkedin.com/feed/update/urn:li:activity:7107058189082230784?commentUrn=urn%3Ali%3Acomment%3A%28activity%3A7107058189082230784%2C7107115917754433536%29&amp;amp;dashCommentUrn=urn%3Ali%3Afsd_comment%3A%287107115917754433536%2Curn%3Ali%3Aactivity%3A7107058189082230784%29"&gt;McK is offering false certainty to replace honest ambiguity&lt;/a&gt;&lt;/q&gt;. Anyway, I think it is rather obvious why advice on software engineering from a company that mainly sells political capital and powerpoint presentations (even if the latter &lt;a href="http://tomwildenhain.com/PowerPointTM/Paper.pdf"&gt;is Turing-complete&lt;/a&gt;...) needs to be taken with more than just a grain of salt.
&lt;/p&gt;

&lt;time datetime="2023-09-15"&gt;&lt;/time&gt;</description><author>Fabian's public notepad</author><pubDate>Fri, 15 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://holzer.online/weeknotes/2023/37/index.html</guid></item><item><title>Ep. 33— That little feature called “Undo”</title><link>https://billylo.medium.com/ep-33-that-little-feature-called-undo-5bae60750de2?source=rss-5717c3e67e86------2</link><description>&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*P66jvwsIR6Zun9CiTpmZVg.jpeg" /&gt;&lt;figcaption&gt;From good to GREAT…&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;A thoughtful design and implementation of an &lt;strong&gt;Undo/Redo &lt;/strong&gt;feature is really valuable to your users. Don’t miss this opportunity to surprise and delight!&lt;/p&gt;&lt;p&gt;Here are a few pointers that you may find useful:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;On iOS/Mac, definitely take advantage of the Foundation &lt;a href="https://developer.apple.com/documentation/foundation/undomanager"&gt;UndoManager&lt;/a&gt; API to keep track of things. It makes your logic easier to follow and the resulting implementation more robust. You can find a good sample &lt;a href="https://nshipster.com/nsundomanager/"&gt;here&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;On Android, there isn’t an official/prescribed approach. This &lt;a href="https://github.com/Muddz/Regret"&gt;Regret&lt;/a&gt; framework does make thing a little easier.&lt;/li&gt;&lt;li&gt;Unity has &lt;a href="https://docs.unity3d.com/ScriptReference/Undo.html"&gt;an Undo class&lt;/a&gt;; Qt has &lt;a href="https://docs.w3cub.com/qt~5.11/qundo/"&gt;QUndo&lt;/a&gt;&lt;/li&gt;&lt;li&gt;JavaScript? I would suggest this &lt;a href="https://github.com/ArthurClemens/Javascript-Undo-Manager"&gt;UndoManager&lt;/a&gt; project on Github.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Hope this helps.&lt;/p&gt;&lt;img alt="" height="1" src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=5bae60750de2" width="1" /&gt;</description><author>Stories by Billy Lo on Medium</author><pubDate>Fri, 15 Sep 2023 00:35:06 GMT</pubDate><guid isPermaLink="true">https://billylo.medium.com/ep-33-that-little-feature-called-undo-5bae60750de2?source=rss-5717c3e67e86------2</guid></item><item><title>PET Business</title><link>https://www.masswerk.at/nowgobang/2023/pet2001-business-keyboard</link><description>PET 2001 emulator up to business.</description><author>mass:werk – Now Go Bang!</author><pubDate>Thu, 14 Sep 2023 14:30:00 GMT</pubDate><guid isPermaLink="true">https://www.masswerk.at/nowgobang/2023/pet2001-business-keyboard</guid></item><item><title>Asking questions the right way</title><link>https://vadimkravcenko.com/shorts/asking-right-questions/</link><description>&lt;p&gt;Question:&lt;/p&gt;
&lt;/p&gt;
&lt;p&gt; Answer:&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://vadimkravcenko.com/shorts/asking-right-questions/"&gt;Asking questions the right way&lt;/a&gt; appeared first on &lt;a href="https://vadimkravcenko.com"&gt;Vadim Kravcenko&lt;/a&gt;.&lt;/p&gt;</description><author>Vadim Kravcenko</author><pubDate>Thu, 14 Sep 2023 09:40:20 GMT</pubDate><guid isPermaLink="true">https://vadimkravcenko.com/shorts/asking-right-questions/</guid></item><item><title>Miscellaneous Azure Notes</title><link>https://ben.bolte.cc/posts/2023-09-14-azure</link><description>Miscellaneous notes about various Azure-related things.</description><author>Ben Bolte</author><pubDate>Thu, 14 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ben.bolte.cc/posts/2023-09-14-azure</guid></item><item><title>Different Types of Means</title><link>https://alexmolas.com/2023/09/14/types-of-means.html</link><description>Understand the different types of means and when to use them.</description><author>Alex Molas Blog</author><pubDate>Thu, 14 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://alexmolas.com/2023/09/14/types-of-means.html</guid></item><item><title>Note 113</title><link>https://qubyte.codes/notes/1694603040384</link><description>&lt;p&gt;Unexpected upside to having a mouse problem in the neighbourhood… a falcon just perched on our garden wall! Unfortunately I wasn’t fast enough to snap a photo of it.&lt;/p&gt;</description><author>Qubyte Codes</author><pubDate>Wed, 13 Sep 2023 14:04:00 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/notes/1694603040384</guid></item><item><title>Spring Events: Mit @EventListener und ApplicationEvents deine Applikation entkoppeln</title><link>https://backendhance.com/blog/2023/spring-events/</link><description>&lt;p&gt;Wenn du schon mit dem &lt;strong&gt;Spring Framework&lt;/strong&gt; arbeitest, ist dir sicherlich aufgefallen, wie mächtig und vielseitig es ist. Eines der nützlichen - aber häufig übersehenen - Features von Spring ist das Event-Handling-System. In diesem Artikel werfen wir einen genaueren Blick darauf, wie du Events im &lt;strong&gt;Spring Framework&lt;/strong&gt; mithilfe von &lt;code&gt;@EventListener&lt;/code&gt; konfigurieren kannst. Zusätzlich zeigen wir dir, wie diese Events in Kombination mit Transaktionen und Asynchronität verwendet werden können.&lt;/p&gt;
&lt;h2 id="warum-events-im-spring-framework"&gt;
&lt;a class="anchor" href="#warum-events-im-spring-framework"&gt;Warum Events im Spring Framework?&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Events sind ein integraler Bestandteil des Spring Frameworks. Sie ermöglichen es, loosely coupled Anwendungen zu erstellen. &lt;strong&gt;Spring Boot&lt;/strong&gt; nutzt Events, um den Status der Applikation zu kommunizieren. Beispiele dafür sind das &lt;code&gt;ApplicationStartedEvent&lt;/code&gt;. Du kannst aber auch eigene Events publizieren. Und das out-of-the-box, ohne Boilerplate und mit wenig Aufwand.&lt;/p&gt;</description><author>Backendhance</author><pubDate>Wed, 13 Sep 2023 08:07:21 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/spring-events/</guid></item><item><title>Miscellaneous AWS Notes</title><link>https://ben.bolte.cc/posts/2023-09-13-aws</link><description>Miscellaneous notes about various AWS-related things.</description><author>Ben Bolte</author><pubDate>Wed, 13 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ben.bolte.cc/posts/2023-09-13-aws</guid></item><item><title>Exporting Prometheus metrics with Axum</title><link>https://ellie.wtf/notes/exporting-prometheus-metrics-with-axum/</link><description>&lt;p&gt;Observability is important! Generally I use Axum as my HTTP framework in &lt;a href="https://ellie.wtf/notes/rust/"&gt;Rust&lt;/a&gt;, as it's pretty ergonomic to use + fast.&lt;/p&gt;
&lt;blockquote class="markdown-alert-note"&gt;
&lt;p&gt;&lt;a href="https://github.com/tower-rs/tower-http" rel="external"&gt;tower-http&lt;/a&gt; provides a bunch of useful HTTP middlewares used in a lot of projects. At the moment it does not provide a metrics middleware. Someday it may do!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/tower-rs/tower-http/issues/57" rel="external"&gt;Issue&lt;/a&gt; to track&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There are quite a few crates that do a lot of this automagically for you, but the &lt;a href="https://github.com/tokio-rs/axum/blob/368c3ee08fc3896358d3bd2bfc8cc67f2925c6ef/examples/prometheus-metrics/src/main.rs" rel="external"&gt;Axum example&lt;/a&gt; suggests using &lt;a href="https://github.com/metrics-rs/metrics" rel="external"&gt;metrics&lt;/a&gt;. I honestly don't need anything extra complex and just want a &lt;code&gt;/metrics&lt;/code&gt; endpoint with some counters/etc most of the time - so metrics it is!&lt;/p&gt;
&lt;p&gt;Anyway, first we need to setup the prometheus exporter. This is basically what generates the content of &lt;code&gt;/metrics&lt;/code&gt;. It uses the &lt;code&gt;metrics-exporter-prometheus&lt;/code&gt; crate. You'll only want to set up a global recorder in an executable - for a library, you can leave that up to the user.&lt;/p&gt;
&lt;p&gt;I've mostly just lifted this from the Axum example linked above 😇&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FB4934;"&gt;fn&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt; setup_metrics_recorder&lt;/span&gt;&lt;span style="color: #A89984;"&gt;()&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; -&amp;gt;&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt; PrometheusHandle&lt;/span&gt;&lt;span style="color: #A89984;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FE8019;"&gt;    const&lt;/span&gt;&lt;span style="color: #D3869B; font-weight: bold;"&gt; EXPONENTIAL_SECONDS&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;: &amp;amp;&lt;/span&gt;&lt;span style="color: #A89984;"&gt;[&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;f64&lt;/span&gt;&lt;span style="color: #A89984;"&gt;]&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; = &amp;amp;&lt;/span&gt;&lt;span style="color: #A89984;"&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #D3869B;"&gt;        0&lt;/span&gt;&lt;span style="color: #A89984;"&gt;.&lt;/span&gt;&lt;span style="color: #D3869B;"&gt;005&lt;/span&gt;&lt;span style="color: #A89984;"&gt;,&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; 0&lt;/span&gt;&lt;span style="color: #A89984;"&gt;.&lt;/span&gt;&lt;span style="color: #D3869B;"&gt;01&lt;/span&gt;&lt;span style="color: #A89984;"&gt;,&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; 0&lt;/span&gt;&lt;span style="color: #A89984;"&gt;.&lt;/span&gt;&lt;span style="color: #D3869B;"&gt;025&lt;/span&gt;&lt;span style="color: #A89984;"&gt;,&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; 0&lt;/span&gt;&lt;span style="color: #A89984;"&gt;.&lt;/span&gt;&lt;span style="color: #D3869B;"&gt;05&lt;/span&gt;&lt;span style="color: #A89984;"&gt;,&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; 0&lt;/span&gt;&lt;span style="color: #A89984;"&gt;.&lt;/span&gt;&lt;span style="color: #D3869B;"&gt;1&lt;/span&gt;&lt;span style="color: #A89984;"&gt;,&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; 0&lt;/span&gt;&lt;span style="color: #A89984;"&gt;.&lt;/span&gt;&lt;span style="color: #D3869B;"&gt;25&lt;/span&gt;&lt;span style="color: #A89984;"&gt;,&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; 0&lt;/span&gt;&lt;span style="color: #A89984;"&gt;.&lt;/span&gt;&lt;span style="color: #D3869B;"&gt;5&lt;/span&gt;&lt;span style="color: #A89984;"&gt;,&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; 1&lt;/span&gt;&lt;span style="color: #A89984;"&gt;.&lt;/span&gt;&lt;span style="color: #D3869B;"&gt;0&lt;/span&gt;&lt;span style="color: #A89984;"&gt;,&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; 2&lt;/span&gt;&lt;span style="color: #A89984;"&gt;.&lt;/span&gt;&lt;span style="color: #D3869B;"&gt;5&lt;/span&gt;&lt;span style="color: #A89984;"&gt;,&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; 5&lt;/span&gt;&lt;span style="color: #A89984;"&gt;.&lt;/span&gt;&lt;span style="color: #D3869B;"&gt;0&lt;/span&gt;&lt;span style="color: #A89984;"&gt;,&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; 10&lt;/span&gt;&lt;span style="color: #A89984;"&gt;.&lt;/span&gt;&lt;span style="color: #D3869B;"&gt;0&lt;/span&gt;&lt;span style="color: #A89984;"&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #A89984;"&gt;    ];&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;    PrometheusBuilder&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;::&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;new&lt;/span&gt;&lt;span style="color: #A89984;"&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #8EC07C;"&gt;        .&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;set_buckets_for_metric&lt;/span&gt;&lt;span style="color: #A89984;"&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;            Matcher&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;::&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;Full&lt;/span&gt;&lt;span style="color: #A89984;"&gt;(&amp;quot;&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;http_requests_duration_seconds&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;.&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;to_string&lt;/span&gt;&lt;span style="color: #A89984;"&gt;()),&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #D3869B; font-weight: bold;"&gt;            EXPONENTIAL_SECONDS&lt;/span&gt;&lt;span style="color: #A89984;"&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #A89984;"&gt;        )&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #8EC07C;"&gt;        .&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;unwrap&lt;/span&gt;&lt;span style="color: #A89984;"&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #8EC07C;"&gt;        .&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;install_recorder&lt;/span&gt;&lt;span style="color: #A89984;"&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #8EC07C;"&gt;        .&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;unwrap&lt;/span&gt;&lt;span style="color: #A89984;"&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #A89984;"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I'm actually setting up two metrics today, but only &lt;code&gt;history_requests_duration_seconds&lt;/code&gt; requires some setup. This is because it is a histogram, and we need to tell the exporter how to bucket the data.&lt;/p&gt;
&lt;p&gt;Once that's done, we can write the axum middleware! (lifted from the example, and modified to compile properly. I'll open a PR)&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #928374; font-style: italic;"&gt;/// Middleware to record some common HTTP metrics&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #928374; font-style: italic;"&gt;/// Generic over B to allow for arbitrary body types (eg Vec&amp;lt;u8&amp;gt;, Streams, a deserialized thing, etc)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #928374; font-style: italic;"&gt;/// Someday tower-http might provide a metrics middleware: https://github.com/tower-rs/tower-http/issues/57&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FB4934;"&gt;pub async fn&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt; track_metrics&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;B&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;gt;(&lt;/span&gt;&lt;span style="color: #83A598;"&gt;req&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;:&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt; Request&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;B&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;gt;,&lt;/span&gt;&lt;span style="color: #83A598;"&gt; next&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;:&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt; Next&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;B&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;gt;)&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;-&amp;gt;&lt;/span&gt;&lt;span style="color: #FB4934;"&gt;impl&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt; IntoResponse&lt;/span&gt;&lt;span style="color: #A89984;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FE8019;"&gt;    let&lt;/span&gt;&lt;span style="color: #83A598;"&gt; start&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; =&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt; Instant&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;::&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;now&lt;/span&gt;&lt;span style="color: #A89984;"&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FE8019;"&gt;    let&lt;/span&gt;&lt;span style="color: #83A598;"&gt; path&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; =&lt;/span&gt;&lt;span style="color: #FB4934;"&gt; if&lt;/span&gt;&lt;span style="color: #FE8019;"&gt; let&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt; Some&lt;/span&gt;&lt;span style="color: #A89984;"&gt;(&lt;/span&gt;&lt;span style="color: #83A598;"&gt;matched_path&lt;/span&gt;&lt;span style="color: #A89984;"&gt;)&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; =&lt;/span&gt;&lt;span style="color: #83A598;"&gt; req&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;.&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;extensions&lt;/span&gt;&lt;span style="color: #A89984;"&gt;()&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;.&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;get&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;::&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;MatchedPath&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #83A598;"&gt;        matched_path&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;.&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;as_str&lt;/span&gt;&lt;span style="color: #A89984;"&gt;()&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;.&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;to_owned&lt;/span&gt;&lt;span style="color: #A89984;"&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #A89984;"&gt;    }&lt;/span&gt;&lt;span style="color: #FB4934;"&gt; else&lt;/span&gt;&lt;span style="color: #A89984;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #83A598;"&gt;        req&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;.&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;uri&lt;/span&gt;&lt;span style="color: #A89984;"&gt;()&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;.&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;path&lt;/span&gt;&lt;span style="color: #A89984;"&gt;()&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;.&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;to_owned&lt;/span&gt;&lt;span style="color: #A89984;"&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #A89984;"&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FE8019;"&gt;    let&lt;/span&gt;&lt;span style="color: #83A598;"&gt; method&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; =&lt;/span&gt;&lt;span style="color: #83A598;"&gt; req&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;.&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;method&lt;/span&gt;&lt;span style="color: #A89984;"&gt;()&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;.&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;clone&lt;/span&gt;&lt;span style="color: #A89984;"&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #928374; font-style: italic;"&gt;    // Run the rest of the request handling first, so we can measure it and get response&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #928374; font-style: italic;"&gt;    // codes.&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FE8019;"&gt;    let&lt;/span&gt;&lt;span style="color: #83A598;"&gt; response&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; =&lt;/span&gt;&lt;span style="color: #83A598;"&gt; next&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;.&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;run&lt;/span&gt;&lt;span style="color: #A89984;"&gt;(&lt;/span&gt;&lt;span style="color: #83A598;"&gt;req&lt;/span&gt;&lt;span style="color: #A89984;"&gt;)&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;.&lt;/span&gt;&lt;span style="color: #FB4934;"&gt;await&lt;/span&gt;&lt;span style="color: #A89984;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FE8019;"&gt;    let&lt;/span&gt;&lt;span style="color: #83A598;"&gt; latency&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; =&lt;/span&gt;&lt;span style="color: #83A598;"&gt; start&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;.&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;elapsed&lt;/span&gt;&lt;span style="color: #A89984;"&gt;()&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;.&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;as_secs_f64&lt;/span&gt;&lt;span style="color: #A89984;"&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FE8019;"&gt;    let&lt;/span&gt;&lt;span style="color: #83A598;"&gt; status&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; =&lt;/span&gt;&lt;span style="color: #83A598;"&gt; response&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;.&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;status&lt;/span&gt;&lt;span style="color: #A89984;"&gt;()&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;.&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;as_u16&lt;/span&gt;&lt;span style="color: #A89984;"&gt;()&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;.&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;to_string&lt;/span&gt;&lt;span style="color: #A89984;"&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FE8019;"&gt;    let&lt;/span&gt;&lt;span style="color: #83A598;"&gt; labels&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; =&lt;/span&gt;&lt;span style="color: #A89984;"&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #A89984;"&gt;        (&amp;quot;&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;method&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;quot;,&lt;/span&gt;&lt;span style="color: #83A598;"&gt; method&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;.&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;to_string&lt;/span&gt;&lt;span style="color: #A89984;"&gt;()),&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #A89984;"&gt;        (&amp;quot;&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;path&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;quot;,&lt;/span&gt;&lt;span style="color: #83A598;"&gt; path&lt;/span&gt;&lt;span style="color: #A89984;"&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #A89984;"&gt;        (&amp;quot;&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;status&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;quot;,&lt;/span&gt;&lt;span style="color: #83A598;"&gt; status&lt;/span&gt;&lt;span style="color: #A89984;"&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #A89984;"&gt;    ];&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;    metrics&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;::&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;increment_counter!&lt;/span&gt;&lt;span style="color: #A89984;"&gt;(&amp;quot;&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;http_requests_total&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;quot;,&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; &amp;amp;&lt;/span&gt;&lt;span style="color: #83A598;"&gt;labels&lt;/span&gt;&lt;span style="color: #A89984;"&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;    metrics&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;::&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;histogram!&lt;/span&gt;&lt;span style="color: #A89984;"&gt;(&amp;quot;&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;http_requests_duration_seconds&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;quot;,&lt;/span&gt;&lt;span style="color: #83A598;"&gt; latency&lt;/span&gt;&lt;span style="color: #A89984;"&gt;,&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; &amp;amp;&lt;/span&gt;&lt;span style="color: #83A598;"&gt;labels&lt;/span&gt;&lt;span style="color: #A89984;"&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #83A598;"&gt;    response&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #A89984;"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, wherever you setup your Axum router, plug in the &lt;code&gt;/metrics&lt;/code&gt; route! You'll need to make sure it's not publicly available.&lt;/p&gt;
&lt;blockquote class="markdown-alert-note"&gt;
&lt;p&gt;I learned about &lt;code&gt;std::future::ready&lt;/code&gt; here! It basically creates a future that is immediately available with a value. For example:&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FE8019;"&gt;let&lt;/span&gt;&lt;span style="color: #83A598;"&gt; f&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; =&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt; std&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;::&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;future&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;::&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;ready&lt;/span&gt;&lt;span style="color: #A89984;"&gt;(&lt;/span&gt;&lt;span style="color: #D3869B;"&gt;1&lt;/span&gt;&lt;span style="color: #A89984;"&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;assert_eq!&lt;/span&gt;&lt;span style="color: #A89984;"&gt;(&lt;/span&gt;&lt;span style="color: #83A598;"&gt;a&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;.&lt;/span&gt;&lt;span style="color: #FB4934;"&gt;await&lt;/span&gt;&lt;span style="color: #A89984;"&gt;,&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; 1&lt;/span&gt;&lt;span style="color: #A89984;"&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/blockquote&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FE8019;"&gt;let&lt;/span&gt;&lt;span style="color: #83A598;"&gt; recorder_handle&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; =&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt; setup_metrics_recorder&lt;/span&gt;&lt;span style="color: #A89984;"&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FE8019;"&gt;let&lt;/span&gt;&lt;span style="color: #83A598;"&gt; router&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; =&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt; Router&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;::&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;new&lt;/span&gt;&lt;span style="color: #A89984;"&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #8EC07C;"&gt;	.&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;route&lt;/span&gt;&lt;span style="color: #A89984;"&gt;(&amp;quot;&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;/metrics&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;quot;,&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt; get&lt;/span&gt;&lt;span style="color: #A89984;"&gt;(&lt;/span&gt;&lt;span style="color: #FB4934;"&gt;move ||&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt; ready&lt;/span&gt;&lt;span style="color: #A89984;"&gt;(&lt;/span&gt;&lt;span style="color: #83A598;"&gt;recorder_handle&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;.&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;render&lt;/span&gt;&lt;span style="color: #A89984;"&gt;())))&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #8EC07C;"&gt;	.&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;layer&lt;/span&gt;&lt;span style="color: #A89984;"&gt;(&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;axum&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;::&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;middleware&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;::&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;from_fn&lt;/span&gt;&lt;span style="color: #A89984;"&gt;(&lt;/span&gt;&lt;span style="color: #83A598;"&gt;track_metrics&lt;/span&gt;&lt;span style="color: #A89984;"&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's pretty much it really!&lt;/p&gt;</description><author>Ellie's Notes</author><pubDate>Wed, 13 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ellie.wtf/notes/exporting-prometheus-metrics-with-axum/</guid></item><item><title>s32 Unix Clock</title><link>https://www.da.vidbuchanan.co.uk/blog/unix-clock.html</link><author>David Buchanan's Blog</author><pubDate>Wed, 13 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.da.vidbuchanan.co.uk/blog/unix-clock.html</guid></item><item><title>Announcing Drift Vector!</title><link>/blog/2023/09/13/announcing-drift-vector.html</link><description>If you’ve run into me in the past month or so, there’s a good chance I may have mentioned that I’ve been working on a new project. That project is… (drumroll)…</description><author>biggles.games</author><pubDate>Wed, 13 Sep 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">/blog/2023/09/13/announcing-drift-vector.html</guid></item><item><title>Beyond the Basics: AWS WAF's Lesser-Known Limitations</title><link>https://badshah.io/blog/aws-waf-lesser-known-limitations-revealed/</link><description>&lt;p&gt;AWS WAF service is an L7 firewall service offered by AWS. It&amp;rsquo;s easy to set up, seamlessly integrates with other AWS services (ALB, API Gateway, etc.), and comes with a handful of managed WAF rulesets and rate limit features.&lt;/p&gt;</description><author>Chandrapal Badshah | Security Engineer on Chandrapal Badshah</author><pubDate>Tue, 12 Sep 2023 21:30:00 GMT</pubDate><guid isPermaLink="true">https://badshah.io/blog/aws-waf-lesser-known-limitations-revealed/</guid></item><item><title>The Say and the Choos: a Distributed Labeling System</title><link>https://noahnorman.substack.com/p/the-say-and-the-choos-a-distributed</link><description>Fleshing out the 'say' and the 'choos' components of the system.</description><author>Augmented Realist</author><pubDate>Tue, 12 Sep 2023 17:15:14 GMT</pubDate><guid isPermaLink="true">https://noahnorman.substack.com/p/the-say-and-the-choos-a-distributed</guid></item><item><title>Using Postman with .net gRPC endpoints</title><link>https://whatibroke.com/2023/09/12/using-postman-with-net-grpc-endpoints/</link><description>Hey everyone, Just a quick post on how to use postman with a gRPC endpoint using .net core. Add the grpc reflection package to your project: Add to the container and include the middleware in your program.cs: Startup your project and then open postman. Create a new gRPC request by: You should now be able &amp;#8230; &lt;a class="more-link" href="https://whatibroke.com/2023/09/12/using-postman-with-net-grpc-endpoints/"&gt;Continue reading&lt;span class="screen-reader-text"&gt; "Using Postman with .net gRPC&amp;#160;endpoints"&lt;/span&gt;&lt;/a&gt;</description><author>What I Broke – Programming and Web Development</author><pubDate>Tue, 12 Sep 2023 16:19:57 GMT</pubDate><guid isPermaLink="true">https://whatibroke.com/2023/09/12/using-postman-with-net-grpc-endpoints/</guid></item><item><title>Mcl (minecraft launcher)</title><link>https://mfashby.net/posts/2023-09-12-mcl/</link><description/><author>Home on mfashby.net</author><pubDate>Tue, 12 Sep 2023 09:03:15 GMT</pubDate><guid isPermaLink="true">https://mfashby.net/posts/2023-09-12-mcl/</guid></item><item><title>Bitbucket pipeline with an elasticsearch server</title><link>https://tomk32.de/2023/09/12/bitbucket-pipeline-with-elasticsearch.html</link><description>This week I’m setting up a bitbucket pipeline for a Ruby on Rails app that uses a few services, among them elastic search. Now starting that service throws up an error as elasticsearch servers are social animals and really like to connect to other elasticsearch servers and you’ll see this erro in the log/tab for the elasticsearch server:</description><author>Thomas R. Koll</author><pubDate>Tue, 12 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://tomk32.de/2023/09/12/bitbucket-pipeline-with-elasticsearch.html</guid></item><item><title>Linear code is more readable</title><link>https://blog.separateconcerns.com/2023-09-11-linear-code.html</link><description>&lt;p&gt;You can only claim to be contrarian if people actually defend the opposite viewpoint. Well, one of the “best practices” I &lt;a href="https://blog.separateconcerns.com/2023-08-06-disagreeing-best-practices.html"&gt;disagree with&lt;/a&gt; was recently &lt;a href="https://testing.googleblog.com/2023/09/use-abstraction-to-improve-function.html"&gt;featured in the Google Testing Blog&lt;/a&gt; — usually a very good resource, there is a reason that post appeared in my newsreader!&lt;/p&gt;
&lt;p&gt;The authors present two versions of a function and ask which one is the most readable.&lt;/p&gt;
&lt;p&gt;&lt;img alt="createPizza" src="img/linear-code.png" /&gt;&lt;/p&gt;
&lt;p&gt;At first glance, we can already notice the presentation is a bit partisan :) In addition to the obvious background color bias, the authors ellipsed some code on the right-hand side, making both implementations appear roughly the same size when the right one is really much longer.&lt;/p&gt;
&lt;p&gt;The authors argue that the function on the right is more readable because it does not mix levels of abstraction, and that makes it more “top-down”. Well, OK, but the function of the left reads linearly from the top of the screen to the bottom, whereas if you want to understand everything that happens on the right-hand side you need to jump back and forth.&lt;/p&gt;
&lt;p&gt;You may tell me, sure, but you never need to look at the whole code, that is what abstraction is for! OK. Quick, tell me this: does the function that bakes the pizza also heat the oven, or do you need to preheat it first? Hint: there are &lt;strong&gt;two&lt;/strong&gt; functions. One is called &lt;code&gt;bake&lt;/code&gt; and takes a Pizza, and the other is called &lt;code&gt;bakePizza&lt;/code&gt;…&lt;/p&gt;
&lt;p&gt;Also, what happens if you pass a pizza to those functions twice? Are they idempotent or do you end up eating cinder?&lt;/p&gt;
&lt;p&gt;So, you may have guessed that I do not like the code style on the right. But I have to admit something: it is somehow easier to understand than the code on the left. Is that because of the structure that separates the levels of abstraction? Let us see. What about that version?&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-go"&gt;func createPizza(order *Order) *Pizza {
  // Prepare pizza
  pizza := &amp;amp;Pizza{Base: order.Size,
                  Sauce: order.Sauce,
                  Cheese: “Mozzarella”}

  // Add toppings
  if order.kind == “Veg” {
    pizza.Toppings = vegToppings
  } else if order.kind == “Meat” {
    pizza.Toppings = meatToppings
  }

  oven := oven.New()
  if oven.Temp != cookingTemp {
    // Heat oven
    for (oven.Temp &amp;lt; cookingTemp) {
      time.Sleep(checkOvenInterval)
      oven.Temp = getOvenTemp(oven)
    }
  }

  if !pizza.Baked {
    // Bake pizza
    oven.Insert(pizza)
    time.Sleep(cookTime)
    oven.Remove(pizza)
    pizza.Baked = true
  }

  // Box and slice
  box := box.New()
  pizza.Boxed = box.PutIn(pizza)
  pizza.Sliced = box.SlicePizza(order.Size)
  pizza.Ready = box.Close()

  return pizza
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do you recognize it? This is just the left-hand side function code, with function names from the right-hand side added as comments.&lt;/p&gt;
&lt;p&gt;I don’t know about you, but this is my favorite. And it looks like the readability just came from explaining what we did properly here, not from extra abstraction layers and indirection.&lt;/p&gt;
&lt;p&gt;In conclusion, I stand by what I said earlier: do not extract small functions from linear code, especially if you only use them once. None of the benefits offsets the loss in linearity.&lt;/p&gt;
&lt;section id="One-more-thing"&gt;
&lt;h2&gt;One more thing&lt;/h2&gt;
&lt;p&gt;I wasn’t sure if I wanted to mention this or not, but I ended up editing the post because there is something that bothers me with this function, and it is that business with the oven.&lt;/p&gt;
&lt;p&gt;First, pre-heating the oven is self-contained and should probably be a method of the oven itself. But beyond that, this code makes no sense: why would you create a whole new oven to make a pizza? In real life, you get an oven &lt;strong&gt;once&lt;/strong&gt;, and then you bake a whole lot of pizzas with it, without going through the whole heating cycle.&lt;/p&gt;
&lt;p&gt;I know this is a synthetic example but this kind of issue actually occurs in real code and sometimes causes performance issues. It is likely that this code should &lt;strong&gt;take&lt;/strong&gt; the oven as a parameter. Providing it is the job of the caller.&lt;/p&gt;
&lt;p&gt;(And since you put the pizza in the box, you probably want an interface where you return the box, not the pizza. Oh well.)&lt;/p&gt;
&lt;/section&gt;</description><author>Separate Concerns</author><pubDate>Mon, 11 Sep 2023 23:35:00 GMT</pubDate><guid isPermaLink="true">https://blog.separateconcerns.com/2023-09-11-linear-code.html</guid></item><item><title>CallerArgumentExpression and extension methods don’t mix</title><link>https://neosmart.net/blog/callerargumentexpression-and-extension-methods-dont-mix/</link><description>&lt;p&gt;This post is for the C# developers out there and takes a look at the interesting conjunction of [CallerArgumentExpression] and static extension methods – a mix that at first seems too convenient to pass up. A quick recap: [CallerArgumentExpression] landed &amp;#8230; &lt;a href="https://neosmart.net/blog/callerargumentexpression-and-extension-methods-dont-mix/"&gt;Continue reading &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
The post &lt;a href="https://neosmart.net/blog/callerargumentexpression-and-extension-methods-dont-mix/"&gt;CallerArgumentExpression and extension methods don’t mix&lt;/a&gt; first appeared on &lt;a href="https://neosmart.net/blog"&gt;The NeoSmart Files&lt;/a&gt;.</description><author>The NeoSmart Files</author><pubDate>Mon, 11 Sep 2023 20:17:55 GMT</pubDate><guid isPermaLink="true">https://neosmart.net/blog/callerargumentexpression-and-extension-methods-dont-mix/</guid></item><item><title>OnlineOrNot Diaries 15</title><link>https://maxrozen.com/onlineornot-diaries-15</link><description>Heading back into the arena. We are so back.</description><author>Max Rozen</author><pubDate>Mon, 11 Sep 2023 20:10:00 GMT</pubDate><guid isPermaLink="true">https://maxrozen.com/onlineornot-diaries-15</guid></item><item><title>How to create a password reset flow for your app.</title><link>https://ethanmick.com/how-to-create-a-password-reset-flow/</link><description>&lt;p&gt;Resetting a password is important to your application&amp;apos;s security and usability. Without a doubt, your app users will sign up and then forget their password.&lt;/p&gt;&lt;p&gt;Nothing is worse than returning to the login page and getting rejected for a wrong password.&lt;/p&gt;&lt;blockquote&gt;What password did I use again? Was&lt;/blockquote&gt;</description><author>Build SaaS with Ethan</author><pubDate>Mon, 11 Sep 2023 18:10:51 GMT</pubDate><guid isPermaLink="true">https://ethanmick.com/how-to-create-a-password-reset-flow/</guid></item><item><title>Steam local network game transfers are a game-changer</title><link>https://ounapuu.ee/posts/2023/09/11/steam-cache/</link><description>Setting up a Steam LAN cache has never been this easy, so I went ahead and did some testing with different configurations.</description><author>./techtipsy</author><pubDate>Mon, 11 Sep 2023 06:00:00 GMT</pubDate><guid isPermaLink="true">https://ounapuu.ee/posts/2023/09/11/steam-cache/</guid></item><item><title>Programmer emotions</title><link>https://zackproser.com/blog/programmer-emotions</link><description>Programmers are people, and this is how I feel when...</description><author>Zachary Proser</author><pubDate>Mon, 11 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/programmer-emotions</guid></item><item><title>Snowflake Connection Provider</title><link>https://backendhance.com/blog/2023/snowflake-connection-provider/</link><description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;p&gt;Was haben Schneeflocken mit Softwareentwicklung zutun?&lt;/p&gt;
&lt;p&gt;Komische Frage. Ich weiß&lt;/p&gt;
&lt;p&gt;Aber ich musste sie mir die Tage stellen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ich durchforstete den Code.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ich wollte verstehen welche Connection Pools wir haben und wie sie konfiguriert sind.&lt;/p&gt;
&lt;p&gt;Dabei stolperte ich über einen Connection Pool, der hieß: &lt;code&gt;SnowflakeConnectionProviders&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Hä?&lt;/p&gt;
&lt;p&gt;Was soll das sein? Und wie wird er verwendet?&lt;/p&gt;
&lt;p&gt;Und warum ist es eine &lt;code&gt;HashMap&amp;lt;String, IConnectionProvider&amp;gt;&lt;/code&gt; ?&lt;/p&gt;
&lt;h2 id="also-find-usage-und-weiterlesen"&gt;
&lt;a class="anchor" href="#also-find-usage-und-weiterlesen"&gt;Also: &lt;code&gt;Find Usage&lt;/code&gt; und weiterlesen.&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Aha, in der &lt;code&gt;init&lt;/code&gt; wird eine kleine pool size konfiguriert. Aber wieso?&lt;/p&gt;</description><author>Backendhance</author><pubDate>Mon, 11 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/snowflake-connection-provider/</guid></item><item><title>A systematic approach to debugging</title><link>https://ntietz.com/blog/how-i-debug-2023/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;I've got a reputation at work as being a skilled debugger.
It's a frequent occurrence that the &lt;em&gt;weird stuff&lt;/em&gt; lands on my desk&lt;sup class="footnote-reference" id="fr-1-1"&gt;&lt;a href="https://ntietz.com/blog/how-i-debug-2023/#fn-1"&gt;[1]&lt;/a&gt;&lt;/sup&gt; after it goes through another skilled engineer or two.
To say my job is substantially "debug the weird shit" would not be an understatement and I'm here for it.&lt;/p&gt;
&lt;p&gt;This extends throughout our codebase, and into code I haven't seen before at all.
I'm the longest tenured engineer at my company, so I'm familiar with most of our systems.
But I've lost track of most of the features that get deployed, and we have way more code changes than I can personally review.
And my debugging spans the stack: backend to frontend to database to weird Ubuntu behavior on our dev laptops.
(Yes, our principal engineer also does tech support, and again, I'm so here for it.)&lt;/p&gt;
&lt;p&gt;So... How do I do it?
If I'm presented routinely with bugs I'm expected to solve in systems I'm unfamiliar with, what's the process?
And does it extend to things outside of code?&lt;/p&gt;
&lt;h1 id="general-approach-to-debugging"&gt;General approach to debugging&lt;/h1&gt;
&lt;p&gt;My approach is systematic and focused on understanding first and foremost.
This is for a variety of reasons, but principally that you need to understand what is going on both to fix it and to be sure it's fixed.&lt;/p&gt;
&lt;p&gt;Here's the process laid out in sequence.
After going through the steps, I'll provide more detail on each one.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Figure out the symptoms.&lt;/li&gt;
&lt;li&gt;Reproduce the bug.&lt;/li&gt;
&lt;li&gt;Understand the system(s).&lt;/li&gt;
&lt;li&gt;Form a hypothesis about where the bug is.&lt;/li&gt;
&lt;li&gt;Test this hypothesis, and repeat if needed.&lt;/li&gt;
&lt;li&gt;Fix the bug! Check the fix, and repeat if needed.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We go through quite a bit of this process before even touching code.
This can feel counter-intuitive and is difficult to get in the habit of, because the instinct is to dive right into the code (reading it and modifying it).
Let's dive into each of these steps in more detail.&lt;/p&gt;
&lt;h2 id="1-figure-out-the-symptoms"&gt;1. Figure out the symptoms&lt;/h2&gt;
&lt;p&gt;First you have to figure out the symptoms: what's the bad behavior that's being read as a bug?
What behaviors are happening that shouldn't, what's going wrong?&lt;/p&gt;
&lt;p&gt;This one sounds obvious but it's a step people skip a lot.&lt;/p&gt;
&lt;p&gt;If you get a bug report, the first thing to do is determine what it means &lt;em&gt;precisely&lt;/em&gt;.
In the best case scenario you will have a well-written issue description already from either the bug reporter or a colleague who triaged it, but even in this case take some time to digest it.
Sit with the bug report and understand what behavior you're trying to address, and play around with the software in question as well.&lt;/p&gt;
&lt;p&gt;If you don't understand the bug behavior, you have &lt;em&gt;no hope&lt;/em&gt; of knowing if you've fixed it or not.
You can't even get started reproducing it!
So this is a crucial step to start with.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Questions to ask:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When did the bug start happening?&lt;/li&gt;
&lt;li&gt;How many people have experienced it? Reported it?&lt;/li&gt;
&lt;li&gt;Who noticed it first?&lt;/li&gt;
&lt;li&gt;What environments does it occur in?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="2-reproduce-the-bug"&gt;2. Reproduce the bug&lt;/h2&gt;
&lt;p&gt;After you know what the bug &lt;em&gt;is&lt;/em&gt;, you sit down and try to reproduce it.
I like to reproduce bugs first in the same environment it was originally seen in, as long as it's safe to do so.
You don't want to mess up real user data in production, but if you can reproduce the bug without harm, definitely do so.&lt;/p&gt;
&lt;p&gt;From there, I like to reduce the reproduction to as minimal steps as possible.
This is also where you can start moving it into environments where you have more control and better tools to inspect the system with&lt;sup class="footnote-reference" id="fr-2-1"&gt;&lt;a href="https://ntietz.com/blog/how-i-debug-2023/#fn-2"&gt;[2]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Each struggle to reproduce the bug tells you more about the bug!
If you try to reduce the reproduction to something smaller, you'll find pieces that are essential for reproducing it (does it happen with all user types, or a particular user type? all workspaces, or one workspace?) and those that are incidental.
This is a starting point for understanding what's going on and will give you hints about what could be the cause.&lt;/p&gt;
&lt;p&gt;Sometimes reproducing the bug can be vexingly difficult.
It's necessary: &lt;em&gt;don't skip this&lt;/em&gt;.
If you cannot reproduce the bug, you cannot confirm whether it's fixed or not.&lt;/p&gt;
&lt;p&gt;Some bugs will be reproducible &lt;em&gt;sometimes&lt;/em&gt; (especially the case for race condition-based bugs).
If that's the case, work to get the reproduction as reliable as possible, and measure the reproduction.
If it happens 1/20 times vs if it happens 1/2 times, it's harder to be confident that you fixed it and didn't just make it less likely.
And when it's truly only reproducible sometimes, automating and measuring your reproduction can give a good way to &lt;em&gt;measure&lt;/em&gt; your progress on the bug.
You can let your automation rip through 10x the necessary cases for reproducing it and see if you really, truly did fix it. Probably.&lt;/p&gt;
&lt;h2 id="3-understand-the-system-s"&gt;3. Understand the system(s)&lt;/h2&gt;
&lt;p&gt;Now that we understand what the bug is and we can reproduce it, we can take a step back to understand the system as a whole.
The instinct at this stage will be to jump in and start doing "proper" debugging with your debugger; resist this temptation, it will bite you.
It's better to take a step back and understand the system first.&lt;/p&gt;
&lt;p&gt;Some of this will be in your head already if you're working in a familiar codebase, but it is beneficial to go through what pieces and parts are involved here.
It will refresh your mental model of the system and load things up into your memory to help you form connections between different components involved.&lt;/p&gt;
&lt;p&gt;These are some of the questions I like to know the answers to when debugging web applications (analogues exist for other software):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What code is currently running?&lt;/li&gt;
&lt;li&gt;When was it last deployed?&lt;/li&gt;
&lt;li&gt;What were the recent changes?&lt;/li&gt;
&lt;li&gt;Does the appearance of the bug coincide with a deployment or another change?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You will also want to look at your logs and observability tools and breathe them in.
You can start with the logs that are relevant to &lt;em&gt;this&lt;/em&gt; error, but you also want to find the logs that are just "normal".
If you don't look at the normal logs, you won't know what normal logs look like; maybe that error you're seeing is actually benign and a bad log message, or maybe it's related!
If you don't look at normal distributed traces, you won't know what weird ones look like!
Until you've gotten your pattern matching for what's normal, you can't tell what's an outlier.
So read through a bit, skim a bit, and let your brain do some pattern matching to prime you for deeper diving.&lt;/p&gt;
&lt;h2 id="4-form-a-hypothesis-about-the-location-of-the-bug"&gt;4. Form a hypothesis about the &lt;em&gt;location&lt;/em&gt; of the bug&lt;/h2&gt;
&lt;p&gt;Now we know enough to start figuring out where the bug is.
Note that at this step we're not worried about &lt;em&gt;what&lt;/em&gt; the bug is, but &lt;em&gt;where&lt;/em&gt; it is:
Which component of our system is causing this bug?
Which module of that component is doing something naughty?&lt;/p&gt;
&lt;p&gt;The main point of this is &lt;em&gt;narrowing the search space&lt;/em&gt;.
Production systems are usually far larger than we can fit in our heads at one time.
By narrowing it down, we can make the context small enough to be able to work more effectively.&lt;/p&gt;
&lt;p&gt;So, what we do is form a hypothesis of &lt;strong&gt;where the bug is&lt;/strong&gt;.
Some questions that we can form hypotheses around:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Which component of our system contains the bug? Is it just one, or multiple?&lt;/li&gt;
&lt;li&gt;Is the bug in the component, or in the interactions between components?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Early on, you want to bisect the system.
Make a hypothesis that allows you to eliminate as many locations as possible, ideally close to 50% of the system.
This lets you do a sort of binary search for the bug and make rapid progress narrowing it down.&lt;/p&gt;
&lt;h2 id="5-test-your-hypothesis"&gt;5. Test your hypothesis&lt;/h2&gt;
&lt;p&gt;Once you have a hypothesis about where the bug is, you can test the hypothesis.
Locate the component in question and validate input/output.
Is the bug here, or is it somewhere else?&lt;/p&gt;
&lt;p&gt;This can be tricky and nuanced, because you might not have full visibility into what's going on to test your hypothesis.
Don't be afraid to &lt;em&gt;modify what's running&lt;/em&gt; to get more information!
A lot of people are nervous to do this, but it's important to remember: &lt;strong&gt;the power of software is that we can &lt;em&gt;change&lt;/em&gt; it&lt;/strong&gt;, including adding more debug logs.
Just make sure you reproduce the bug again after your modifications, otherwise your changes may hide the bug even if apparently unrelated&lt;sup class="footnote-reference" id="fr-3-1"&gt;&lt;a href="https://ntietz.com/blog/how-i-debug-2023/#fn-3"&gt;[3]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Now we repeat until we find the location of the bug and zero in on it.
Whether you validate or invalidate your hypothesis, you gain information which lets you construct another, narrower, hypothesis!
We keep going back to forming hypotheses (or gathering more information) until we are quite close to the bug.
As you repeat, you may shift from location to behavior-based hypotheses; this is natural and okay as long as you keep gaining information and not just ruling out one particular cause of the bug.&lt;/p&gt;
&lt;h2 id="6-fix-the-bug"&gt;6. Fix the bug!&lt;/h2&gt;
&lt;p&gt;Now we get to the final stage.
We know what the bug is, how to reproduce it, how the system works, and where the bug is.
All that's left is to fix it!&lt;/p&gt;
&lt;p&gt;This is hopefully the easy part once you've gotten here.
If it's a "simple" bug, then this is straightforward coding.
Sometimes the bug belies a deficiency in the design of the system, and then it's a lot more challenging to fix, but at least you're armed with the information you need to fix or mitigate it.&lt;/p&gt;
&lt;p&gt;This stage may also sometimes kick you back to an earlier stage, if attempting to fix it reveals that it's not where you thought or that there are other interacting pieces.
You might be going back and repeating steps, but it's all forward progress.
Repeat as many times as needed.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;That's my general process!
One of the things I like about it is that it isn't specific to software at all, outside of tools you choose to use.
You can apply this process to debugging systems in general, and it's a good systematic approach to problem solving.
You learn a &lt;em&gt;lot&lt;/em&gt; along the way, too!&lt;/p&gt;
&lt;hr /&gt;
&lt;hr /&gt;&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-1"&gt;
&lt;p&gt;When I returned from my sabbatical at RC, there were a couple of bugs where people said "oh, we were saving this one for when you got back!" &lt;a href="https://ntietz.com/blog/how-i-debug-2023/#fr-1-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-2"&gt;
&lt;p&gt;This does assume that you have less restricted access on your local environment than production. You don't have root in prod... right? &lt;a href="https://ntietz.com/blog/how-i-debug-2023/#fr-2-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-3"&gt;
&lt;p&gt;Gotta love these ones, and there's a term for them: &lt;a href="https://en.wikipedia.org/wiki/Heisenbug"&gt;Heisenbugs&lt;/a&gt;. &lt;a href="https://ntietz.com/blog/how-i-debug-2023/#fr-3-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 11 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/how-i-debug-2023/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>CLI tip 33: manipulating string case with GNU sed</title><link>https://learnbyexample.github.io/tips/cli-tip-33/</link><description>&lt;p&gt;&lt;code&gt;sed&lt;/code&gt; provides escape sequences to change the case of replacement strings, which might include backreferences, shell variables, etc.&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Sequence&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\E&lt;/code&gt;&lt;/td&gt;&lt;td&gt;indicates the end of case conversion&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\l&lt;/code&gt;&lt;/td&gt;&lt;td&gt;convert the next character to lowercase&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\u&lt;/code&gt;&lt;/td&gt;&lt;td&gt;convert the next character to uppercase&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\L&lt;/code&gt;&lt;/td&gt;&lt;td&gt;convert the following characters to lowercase (overridden by &lt;code&gt;\U&lt;/code&gt; or &lt;code&gt;\E&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\U&lt;/code&gt;&lt;/td&gt;&lt;td&gt;convert the following characters to uppercase (overridden by &lt;code&gt;\L&lt;/code&gt; or &lt;code&gt;\E&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;First up, changing case of only the immediate next character after the escape sequence.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# match only the first character of a word
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# use &amp;amp; to backreference the matched character
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# \u would then change it to uppercase
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'hello there. how are you?' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/\b\w/\u&amp;amp;/g'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Hello There&lt;/span&gt;&lt;span&gt;. &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;How Are You&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;?
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# change the first character of a word to lowercase
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'HELLO THERE. HOW ARE YOU?' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/\b\w/\l&amp;amp;/g'
&lt;/span&gt;&lt;span&gt;hELLO tHERE. hOW aRE yOU?
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# match lowercase followed by underscore followed by lowercase
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# delete the underscore and convert the 2nd lowercase to uppercase
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'_fig aug_price next_line' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;E &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/([a-z])_([a-z])/\1\u\2/g'
&lt;/span&gt;&lt;span&gt;_fig augPrice nextLine
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, changing case of multiple characters at a time.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# change all alphabets to lowercase
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'HaVE a nICe dAy' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/.*/\L&amp;amp;/'
&lt;/span&gt;&lt;span&gt;have a nice day
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# change all alphabets to uppercase
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'HaVE a nICe dAy' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/.*/\U&amp;amp;/'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;HAVE A NICE DAY
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# \E will stop further conversion
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fig_ aug_price next_line' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;E &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/([a-z]+)(_[a-z]+)/\U\1\E\2/g'
&lt;/span&gt;&lt;span&gt;fig_ &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;AUG_price NEXT_line
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# \L or \U will override any existing conversion
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'HeLLo:bYe gOoD:beTTEr' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;E &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/([a-z]+)(:[a-z]+)/\L\1\U\2/Ig'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;hello:&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;BYE &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;good:&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;BETTER
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, examples where escapes are used next to each other.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# uppercase first character of a word
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# and lowercase rest of the word characters
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# note the order of escapes used, \u\L won't work
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'HeLLo:bYe gOoD:beTTEr' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;E &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/[a-z]+/\L\u&amp;amp;/Ig'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;Hello:&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Bye &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;Good:&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Better
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# lowercase first character of a word
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# and uppercase rest of the word characters
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'HeLLo:bYe gOoD:beTTEr' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;E &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/[a-z]+/\U\l&amp;amp;/Ig'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;hELLO:&lt;/span&gt;&lt;span&gt;bYE &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;gOOD:&lt;/span&gt;&lt;span&gt;bETTER
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/learn_gnused"&gt;CLI text processing with GNU sed&lt;/a&gt; ebook.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Mon, 11 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/cli-tip-33/</guid></item><item><title>Eternal Robustness</title><link>https://neil.computer/notes/eternal-robustness/</link><description>"Look, you can trust us. We can be depended upon. We are serious about our products and we take great care to provide you with excellence year after year. Your involvement with us will be predictable and stable."</description><author>Neil Panchal</author><pubDate>Mon, 11 Sep 2023 00:34:29 GMT</pubDate><guid isPermaLink="true">https://neil.computer/notes/eternal-robustness/</guid></item><item><title>The AWK book's 60-line version of Make</title><link>https://benhoyt.com/writings/awk-make/</link><description>A discussion of the minimal version of Make included in 'The AWK Programming Language', as well as a Python port of the AWK program.</description><author>Ben Hoyt's technical writing</author><pubDate>Mon, 11 Sep 2023 00:00:00 GMT</pubDate><guid isPermaLink="true">https://benhoyt.com/writings/awk-make/</guid></item><item><title>What’s wrong with the baby’s room?</title><link>https://medium.com/@k0ryk/whats-wrong-with-the-baby-s-room-27bbdd0ff07?source=rss-3cf45d4f3676------2</link><description>&lt;h4&gt;Dad’s Data Driven Delusions&lt;/h4&gt;&lt;p&gt;The couple walking their dogs are staring at my house, mouths slightly agape. A bright orange seal is being fitted to my front door. It’s missing the hazmat symbols, but otherwise looks like some sort of containment procedure. “It’s almost exactly the same a what they use for decontamination at the hospital” the energy auitor says. It’s called a &lt;em&gt;Door Blower Test&lt;/em&gt;, it was my latest step in trying to figure out &lt;strong&gt;&lt;em&gt;what’s wrong with the baby’s room?&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/992/1*qBm1otx1TWg6P03lDotEQQ.jpeg" /&gt;&lt;figcaption&gt;The fan on the bottom has multiple rings to configure different volumes of airflow. Source: me&lt;/figcaption&gt;&lt;/figure&gt;&lt;h4&gt;The Beginning&lt;/h4&gt;&lt;p&gt;The first few months after my son was born. I would lie awake at night, sweaty and panicked, looking at a dashboard I built on my phone; hidden under the sheets in my bed to shield the brightness. The dashboard was one I had made to show our indoor air quality; to help give me an understanding of how stuffy the room was. It was very stuffy, over 3000 PPM of CO2.&lt;/p&gt;&lt;p&gt;SIDS is a frightening prospect for a parent. The idea that your child could be suddenly taken from you through a syndrome we don’t completely understand despite decades of research is terrifying. I wanted to understand how to minimize the risk. Some of the decades of research I read focused on &lt;a href="https://pubmed.ncbi.nlm.nih.gov/9261495/"&gt;CO2 levels and SIDS infant mortality&lt;/a&gt;. It is commonly known that CO2 levels over 2000 can cause physical symptoms like nausea and headaches. More recent research seems even more concerning to a layman parent like me:&lt;/p&gt;&lt;blockquote&gt;It has been proposed, among other possibilities, that there is an impaired CO2-arousal system in SIDS-susceptible babies such that when they rebreathe CO2 as described above, they do not arouse, and thus do not cry out, and the blockage is not corrected [&lt;a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC6512334/#R16"&gt;16&lt;/a&gt;,&lt;a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC6512334/#R32"&gt;32&lt;/a&gt;].&lt;/blockquote&gt;&lt;p&gt;&lt;a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC6512334/"&gt;This&lt;/a&gt; was one the many research abstracts that I read to help convince myself that the line on the dashboard representing CO2 could kill my child. The solution was simple, open the door to the bedroom. It may let the cats in and disrupt the comfortable temperature, but it’s better than headache induced nausea or SIDS. As a new parent, I sought the illusion of control over the things that scared me most. As my son grew older, my concerns changed and so did the data I looked at.&lt;/p&gt;&lt;h4&gt;The Baby’s Room&lt;/h4&gt;&lt;p&gt;At 6 months old, we moved our son into the cozy room next to ours. I put some monitoring into his 100 foot room: two cameras, two temperature sensors, humidity, pressure, CO2, VOC, and Radon sensors. In addition, he wore an Owlet sock that measured his pulse, movement and blood oxygenation. He was pretty heavily monitored.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/500/0*z4i7r965yKLSPVA8" /&gt;&lt;figcaption&gt;The SNOO. I would recommend for any data-driven dad. Source: happiestbaby.com&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;He was moving out of his previous bed — the SNOO; the only device FDA-approved to safely facilitate a supine position for a child that can’t roll over — an important SIDS risk reduction. If you’ve ever seen a kid in one of these things, it kind of looks like a torture device. In my experience, it was a huge help and I give SNOO lots of credit for my son’s early sleep training. SNOO’s API allowed me to automatically post an updated version of the graph below to my website every day, giving me hope for long-term trends.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/700/0*haeniYRDPk5s1Ify.png" /&gt;&lt;figcaption&gt;Longest time without waking/crying per night over the 6 months of SNOO usage. Source: Me&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;The new instrumentation was sufficient, and I got mixed reactions from people I talked to about it; most were interested, but someone told me it reminded them of the &lt;a href="https://en.wikipedia.org/wiki/Arkangel_(Black_Mirror)"&gt;Arkangel&lt;/a&gt; episode of Black Mirror. The data helped us figure out a few things that could be affecting comfort (like low humidity), but there was one metric that continued to elude us — &lt;em&gt;temperature.&lt;/em&gt;&lt;/p&gt;&lt;h4&gt;Why’s it so cold in the Baby’s room?&lt;/h4&gt;&lt;p&gt;We tried many things to stabilize the temperature in the baby’s room. In the end, we just got him warmer/cooler pajamas for different times of the year and that was the easiest solution that made him comfortable. Even though he was comfortable, it didn’t stop us from trying to improve the temperature.&lt;/p&gt;&lt;p&gt;First, we noticed a leak in the duct underneath his vent. We fixed it, but it didn’t have as much of an impact as expected. When it was cold, it felt drafty near the window. We sealed the entire window with a plastic wrap insulation kit; it helped slightly, but not as much as expected. Most recently, I added an Ecobee temperature sensor to his room so the thermostat will heat/cool based on my son’s room’s temperature. That’s stabilized things a little bit, but it’s also been nice weather and needs further testing. I was not satisfied with our solutions up to that point.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Jg3Xc2zPMu7JI4yStFrinw.png" /&gt;&lt;figcaption&gt;Past 3 months of temp data for his room. Source: Me&lt;/figcaption&gt;&lt;/figure&gt;&lt;h4&gt;Energy Assessment&lt;/h4&gt;&lt;p&gt;My county provides home energy assessments for $60. For me, this was a three hour comprehensive investigation of my entire house. I am so thankful for this opportunity and the fact my county subsidizes it. Here is what I know about the temperature problems in the baby’s room post-assessment:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;There is leakage through a few small holes around the bottom of the window apron.&lt;/li&gt;&lt;li&gt;There is limited exhaust to the hallway, making it harder to vent heat in as pressure builds up&lt;/li&gt;&lt;li&gt;There is a little leakage around the joist in the floor&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;My energy assessor was a very friendly and knowledgeable professional. He invited me to follow him around and ask as many questions as I had, saying it was way more fun that way.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/990/1*5H_kdZ-H3OII1ZfmkdQW5g.png" /&gt;&lt;figcaption&gt;When insulation is grey, it means it’s acting as a filter for dust and there’s leakage. Source: Me&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;strong&gt;Energy Assessment Steps:&lt;/strong&gt;&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Measure the house for volume calculations&lt;/li&gt;&lt;li&gt;Door Blower Tests&lt;/li&gt;&lt;li&gt;Smoke Pen Test&lt;/li&gt;&lt;li&gt;Hot Water Heater Backdraft Test&lt;/li&gt;&lt;li&gt;Furnace Combustion Test&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;The door blower test was so helpful for me. He used it to depressurize the house. This made it so any hole that had significant air leakage could easily be felt, since the pressure difference between the inside and outside was so huge. While the house was depressurized, we went through every room with a thermal camera and smoke pen. The smoke pen creates a small puff of smoke to more easily visualize air movement around a leak.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/992/1*gj2Ta_0hMmZBWySxYO1WVw.jpeg" /&gt;&lt;figcaption&gt;What’s wrong with the baby’s room? It’s these holes. Smoke test showing the leak here. Source: me&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Windows are points of temperature transfer. That means there will often be movement of air where the warm indoor air meets the cold glass. Often times this air movement is misinterpreted as a leak. My assessor told me that windows are often the last thing that he recommends gets replaced. In my case, the fix will be easy.&lt;/p&gt;&lt;p&gt;“Most leaks are the holes in the walls” he said. I was thinking of punched-through drywall, he continued “outlets, switches, lights, windows, anything where a hole had to be cut.” I learned so much from this man, and it was clear that a large part of his job was answering questions from uninformed home owners. I could tell he is a great educator by his ability to articulate things really simply and use real-world examples backed up with instruments. Energy assessment with this guy gets 5/5 stars ⭐⭐⭐⭐⭐.&lt;/p&gt;&lt;h4&gt;Fixing The Baby’s Room&lt;/h4&gt;&lt;p&gt;In the end, we have a few leads on what’s wrong with the baby’s room. It’s pretty straightforward to fix it:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Caulk the window apron where the holes are.&lt;/li&gt;&lt;li&gt;Spray foam the joist accessible through the drop ceiling in the basement&lt;/li&gt;&lt;li&gt;Cut 1–2 inches off the bottom of the door or add a vent above door.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;I am optimistic for these fixes to help keep our son’s room a stable temperature, but will wait until the data comes in. I often attempt to understand a problem by understanding data. This works well in software, but not so much for continuous complex systems like the microclimate of my house. Sometimes the best way to solve our problems or to make leaps and bounds in our own understanding is to consult an expert. Thank you for reading.&lt;/p&gt;&lt;img alt="" height="1" src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=27bbdd0ff07" width="1" /&gt;</description><author>Stories by Kory Kirk on Medium</author><pubDate>Sun, 10 Sep 2023 15:55:27 GMT</pubDate><guid isPermaLink="true">https://medium.com/@k0ryk/whats-wrong-with-the-baby-s-room-27bbdd0ff07?source=rss-3cf45d4f3676------2</guid></item><item><title>📗 When the Heavens Went on Sale, by Ashlee Vance</title><link>https://ernest.oppet.it/2023/09/10/%f0%9f%93%97-when-the-heavens-went-on-sale-by-ashlee-vance/</link><description>I was excited to read this book for two reasons: This book explains why we are in a new space renaissance and profiles four companies which illustrate this new wave: Planet Labs, Rocket Lab, Astra, Firefly Aerospace. I learned that it is especially tough to make it as a private space company as: This makes [&amp;#8230;]</description><author>Ernest Oppetit</author><pubDate>Sun, 10 Sep 2023 15:08:49 GMT</pubDate><guid isPermaLink="true">https://ernest.oppet.it/2023/09/10/%f0%9f%93%97-when-the-heavens-went-on-sale-by-ashlee-vance/</guid></item><item><title>SHA-256 hash from URL, the easy way</title><link>https://xenodium.com/sha-256-hash-from-url-the-easy-way</link><description>&lt;p&gt;From time to time, I need to generate a SHA-256 hash from a file hosted on some server. For me, this flow typically goes something along the lines of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Copy the file URL from browser.&lt;/li&gt;
&lt;li&gt;Drop to Emacs eshell.&lt;/li&gt;
&lt;li&gt;Change current directory.&lt;/li&gt;
&lt;li&gt;Type &amp;quot;curl -o file&amp;quot;&lt;/li&gt;
&lt;li&gt;Paste the file URL.&lt;/li&gt;
&lt;li&gt;Run curl command.&lt;/li&gt;
&lt;li&gt;Type &amp;quot;shasum -a 256 file&amp;quot;.&lt;/li&gt;
&lt;li&gt;Run &lt;a href="https://linux.die.net/man/1/shasum"&gt;shasum&lt;/a&gt; command.&lt;/li&gt;
&lt;li&gt;Copy the generated hash.&lt;/li&gt;
&lt;li&gt;Maybe delete the downloaded file?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We can maybe shave some steps off by downloading directly from the browser, though that may also bring additional clicks and navigating to a download location.&lt;/p&gt;
&lt;p&gt;Amongst the steps, &lt;a href="https://linux.die.net/man/1/shasum"&gt;shasum&lt;/a&gt; is the star player, and its output can be seen below.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;shasum -a 256 path/to/downloaded/file
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Not a huge deal. One can copy the hash from the output, but why go through multiple small manual steps when I know I can get Emacs to simplify the lot? I've expedited a similar flow in the past when &lt;a href="https://xenodium.com/emacs-clone-git-repo-from-clipboard/"&gt;cloning git repos&lt;/a&gt;. Let's simplify again so hashing a hosted file boils down to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Copy the file URL from browser.&lt;/li&gt;
&lt;li&gt;Run an Emacs interactive command.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is where I pull out &lt;a href="https://github.com/xenodium/dwim-shell-command"&gt;dwim-shell-command&lt;/a&gt; (a little package I wrote) and glue the lot to get an expedited experience.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/sha-256-hash-from-url-the-easy-way/hash.gif" /&gt;&lt;/p&gt;
&lt;p&gt;There isn't much to the function other than glueing a little elisp and a shell script via &lt;code&gt;dwim-shell-command&lt;/code&gt; for some buffer/error handling.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(defun dwim-shell-commands-sha-256-hash-file-at-clipboard-url ()
  &amp;quot;Download file at clipboard URL and generate SHA-256 hash.&amp;quot;
  (interactive)
  (let ((url (current-kill 0)))
    (unless (string-match-p &amp;quot;^http[s]?://&amp;quot; url)
      (user-error &amp;quot;No URL in clipboard&amp;quot;))
    (dwim-shell-command-on-marked-files
     &amp;quot;Generate SHA-256 hash from clipboard URL.&amp;quot;
     (format
      &amp;quot;temp_file=$(mktemp)
       function cleanup {
         rm -f $temp_file
       }
       trap cleanup EXIT
       curl --no-progress-meter --location --fail --output $temp_file %s || exit 1
       shasum -a 256 $temp_file | awk '{print $1}'&amp;quot;
      (shell-quote-argument url))
     :utils '(&amp;quot;curl&amp;quot; &amp;quot;shasum&amp;quot;)
     :on-completion
     (lambda (buffer process)
       (if-let ((success (= (process-exit-status process) 0))
                (hash (with-current-buffer buffer
                        (string-trim (buffer-string)))))
           (progn
             (kill-buffer buffer)
             (kill-new hash)
             (message &amp;quot;Copied %s to clipboard&amp;quot;
                      (propertize hash 'face 'font-lock-string-face)))
         (switch-to-buffer buffer))))))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;dwim-shell-commands-sha-256-hash-file-at-clipboard-url&lt;/code&gt; is now in &lt;a href="https://github.com/xenodium/dwim-shell-command/blob/main/dwim-shell-commands.el"&gt;dwim-shell-commands.el&lt;/a&gt;, the optional counterpart in &lt;a href="https://github.com/xenodium/dwim-shell-command/"&gt;dwim-shell-command&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;UPDATE&lt;/h2&gt;
&lt;p&gt;There's better way. Thanks to &lt;a href="https://emacs.ch/@pkal"&gt;Philip Kaludercic&lt;/a&gt; for &lt;a href="https://emacs.ch/@pkal/111041928308815477"&gt;suggesting&lt;/a&gt; &lt;code&gt;curl -s example.com | sha256sum - | cut -d &amp;quot; &amp;quot; -f1&lt;/code&gt; and &lt;a href="https://emacs.ch/@sachac"&gt;Sacha Chua&lt;/a&gt; who pinged me about it.&lt;/p&gt;
&lt;p&gt;Also note I'm now relying on the &lt;code&gt;&amp;lt;&amp;lt;cb&amp;gt;&amp;gt;&lt;/code&gt; template, since dwim-shell-command replaces it with the clipboard/kill ring.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(defun dwim-shell-commands-sha-256-hash-file-at-clipboard-url ()
  &amp;quot;Download file at clipboard URL and generate SHA-256 hash.&amp;quot;
  (interactive)
  (unless (string-match-p &amp;quot;^http[s]?://&amp;quot; (current-kill 0))
    (user-error &amp;quot;No URL in clipboard&amp;quot;))
  (dwim-shell-command-on-marked-files
   &amp;quot;Generate SHA-256 hash from clipboard URL.&amp;quot;
   &amp;quot;curl -s '&amp;lt;&amp;lt;cb&amp;gt;&amp;gt;' | sha256sum - | cut -d ' ' -f1&amp;quot;
   :utils '(&amp;quot;curl&amp;quot; &amp;quot;sha256sum&amp;quot;)
   :on-completion
   (lambda (buffer process)
     (if-let ((success (= (process-exit-status process) 0))
              (hash (with-current-buffer buffer
                      (string-trim (buffer-string)))))
         (progn
           (kill-buffer buffer)
           (kill-new hash)
           (message &amp;quot;Copied %s to clipboard&amp;quot;
                    (propertize hash 'face 'font-lock-string-face)))
       (switch-to-buffer buffer)))))
&lt;/code&gt;&lt;/pre&gt;</description><author>xenodium.com @alvaro</author><pubDate>Sun, 10 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/sha-256-hash-from-url-the-easy-way</guid></item><item><title>A week in Balamory / Tobermory (Isle of Mull)</title><link>https://www.planetjones.net/blog/10-09-2023/a-week-in-balamory-or-tobermory-isle-of-mull.html</link><description>Tobermory was the filming location for the Balamory children&amp;rsquo;s TV show. We visited for one week to find the Balamory houses, enjoy some great food and take in all of the wonderful nature and wildlife on Scotland&amp;rsquo;s Isle of Mull.</description><author>Jonathan Jones homepage: planetjones.net</author><pubDate>Sun, 10 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.planetjones.net/blog/10-09-2023/a-week-in-balamory-or-tobermory-isle-of-mull.html</guid></item><item><title>Transcribing with AWS Textract</title><link>https://maxleiter.com/blog/transcribing-typewriter</link><description>&lt;p&gt;My family has (in my opinion) a very cool written history of our family tree on my father's side.&lt;br /&gt;Recently, my grandfather found a renewed interest in the book and decided to digitize and update it.&lt;br /&gt;He took it to a local print shop and had them scan it to a PDF (it's 60+ pages), which resulted in scans like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt="typewriter scan" src="/blog/typewriter/typewriter-scan.png?width=350" /&gt;&lt;/p&gt;
&lt;p&gt;It seems like something for &lt;abbr title="optical character recognition"&gt;OCR&lt;/abbr&gt;, but no tool I tried could handle the font and the scan quality very well.&lt;br /&gt;I tried to perform some post-processing on the text to fix the errors, but I couldn't put together the right incantation of imagemagick parameters to do much.&lt;br /&gt;Sharpening sometimes made it easier for me to read it, but it didn't help the OCR tools.&lt;/p&gt;
&lt;p&gt;Here's the first paragraph transcribed my be:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;On or about 1788 in a small town of Streliska Galitsia a
family by the name of Wolf sin Mordecai was living with his
Wife and three sons ;- Berl, Lippe, and Mordecai.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First, I tried my favorite OCR tool: Preview on macOS. It works well for printed text, but it didn't work well with the typewriter's font and scan quality.&lt;br /&gt;Here's a copy pasted excerpt from the first three lines of image above:&lt;/p&gt;

  On or about 1788 la a san22 tom of Strollaka Calissim
  &lt;br /&gt;
  wife and saree sons z-Barl, • Mappo, sad Losdeena.


&lt;p&gt;It completely skips the second line and jumbles the rest into nonsense.&lt;/p&gt;
&lt;p&gt;After that I did some googling and found &lt;a href="https://ocrmypdf.readthedocs.io/en/latest/" rel="noopener noreferrer" target="_blank"&gt;`ocrmypdf`&lt;/a&gt;, which is a command-line wrapper around the &lt;a href="https://github.com/tesseract-ocr/tesseract" rel="noopener noreferrer" target="_blank"&gt;Tesseract&lt;/a&gt; OCR engine.&lt;br /&gt;From what I can tell, most open-source OCR tools are wrappers around Tesseract, so I chose the most popular wrapper I could find.&lt;br /&gt;ocrmypdf did an impressively better job than Preview, but it still wasn't great:&lt;/p&gt;

  On of about 1768 in © omehl town of Strelisks Galitsia
  family by the mame of Wolf sin Moerdessi was living with his
  Wife snd three sons ;~- Berl, Lippe, and Mordecad,


&lt;p&gt;Finally, I decided to try &lt;a href="https://aws.amazon.com/textract/" rel="noopener noreferrer" target="_blank"&gt;AWS Textract&lt;/a&gt; after seeing a Google ad for it.&lt;br /&gt;AWS might seem a little overkill, and it probably is, but their free tier is very generous with 1,000 pages per month and I figured there was a low chance of me going over that.&lt;br /&gt;Not being an AWS user I found it unnecessarily complicated getting started, but once I got it working it was the best solution by far (although still not as great as I expected).&lt;/p&gt;
&lt;p&gt;I first had to create a bucket and upload the PDF to it. I tried to use the AWS CLI for this, but gave up and used the console &lt;code&gt;¯_(ツ)_/¯&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then with the I could run the following command to process the file:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;aws textract analyze-document \
    --document '{&amp;quot;S3Object&amp;quot;:{&amp;quot;Bucket&amp;quot;:&amp;quot;bucketname&amp;quot;,&amp;quot;Name&amp;quot;:&amp;quot;filename&amp;quot;}}' \
    --region us-west-1  \
    --feature-types '[&amp;quot;TABLES&amp;quot;,&amp;quot;FORMS&amp;quot;,&amp;quot;SIGNATURES&amp;quot;]'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;...or so I thought:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;An error occurred (DocumentTooLargeException) when calling the AnalyzeDocument operation: S3 object size 31251609 is more than the maximum limit 10485760
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I guess I should have read the docs more closely. The maximum file size for &lt;em&gt;synchronously&lt;/em&gt; OCRing is 10MB. I could have split the file into smaller chunks,&lt;br /&gt;but instead I fired off a job to process the file async:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;aws textract start-document-analysis \
     --document '{&amp;quot;S3Object&amp;quot;:{&amp;quot;Bucket&amp;quot;:&amp;quot;bucketname&amp;quot;,&amp;quot;Name&amp;quot;:&amp;quot;filename&amp;quot;}}' \
     --feature-types '[&amp;quot;TABLES&amp;quot;,&amp;quot;FORMS&amp;quot;,&amp;quot;SIGNA
TURES&amp;quot;]' \
     -region us-west-1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This returned a response:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-json"&gt;{
  &amp;quot;JobId&amp;quot;: &amp;quot;somerandomstring&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I could then use the job ID to check the status of the job:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;aws textract get-document-analysis \
                  --job-id somerandomstring \
                  --region us-west-1
{
    &amp;quot;JobStatus&amp;quot;: &amp;quot;IN_PROGRESS&amp;quot;,
    &amp;quot;AnalyzeDocumentModelVersion&amp;quot;: &amp;quot;1.0&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once it was done I got back a scary JSON object:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-json"&gt;{
  &amp;quot;DocumentMetadata&amp;quot;: {
    &amp;quot;Pages&amp;quot;: 70
  },
  &amp;quot;JobStatus&amp;quot;: &amp;quot;SUCCEEDED&amp;quot;,
  &amp;quot;NextToken&amp;quot;: &amp;quot;lqcceE45FaQ/PZn9Vh3lEx2gqQuNgqJvcy2HG4g2BClYNBKxaTVRukM41e5+MN7dcASSArbPT1KFXhNRcKa9aGdwut1Yrae234pofBDcFNT6jh9PiogjrsdIaIAnKKNReID8RCk=&amp;quot;,
  &amp;quot;Blocks&amp;quot;: [
    {
      &amp;quot;BlockType&amp;quot;: &amp;quot;PAGE&amp;quot;,
      &amp;quot;Geometry&amp;quot;: {
        &amp;quot;BoundingBox&amp;quot;: {
          &amp;quot;Width&amp;quot;: 0.9691011905670166,
          &amp;quot;Height&amp;quot;: 0.9765985012054443,
          &amp;quot;Left&amp;quot;: 0.0308988057076931,
          &amp;quot;Top&amp;quot;: 0.0
        },
        &amp;quot;Polygon&amp;quot;: [
          {
            &amp;quot;X&amp;quot;: 0.03162039443850517,
            &amp;quot;Y&amp;quot;: 0.0
          }
        ]
      }
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;del&gt;I&lt;/del&gt; Copilot threw together this script to extract the text from the JSON:&lt;/p&gt;
&lt;details&gt;
Click to expand

&lt;pre&gt;&lt;code class="language-js"&gt;const AWS = require('aws-sdk')
const fs = require('fs/promises')

AWS.config.update({ region: 'us-west-1' })

const textract = new AWS.Textract()

const params = {
  JobId: 'somerandomstring',
}

async function getText(params) {
  const data = await textract.getDocumentAnalysis(params).promise()
  let finalText = ''
  const blocks = data.Blocks
  blocks.forEach((block) =&amp;gt; {
    if (block.BlockType === 'LINE') {
      finalText += block.Text + '\n'
    }
  })
  const nextToken = data.NextToken
  if (nextToken) {
    params.NextToken = nextToken
    console.log('Getting next page of results...')
    return finalText + (await getText(params))
  } else {
    return finalText
  }
}

getText(params).then((text) =&amp;gt; {
  fs.writeFile('text_output.txt', text, 'utf8')
  console.log('Done')
})
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;p&gt;Which gave me the best results of all the solutions I tried:&lt;/p&gt;

  On oz about 1788 in a small town of Stroliska Calitais a
  family by the name of Welf sin Mordaoas was living with his
  Wife and three sons :- Barl, Lippe, and Mordecai.



&lt;p&gt;Still not perfect, but it's enough for me to be able to fix the errors and get the text into a format I can use.&lt;br /&gt;If you have any suggestions for improving the results, please let me know &amp;mdash; it will save me lots of time!&lt;/p&gt;</description><author>Max Leiter</author><pubDate>Sun, 10 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://maxleiter.com/blog/transcribing-typewriter</guid></item><item><title>The Ultimate Afterparty Season 2 Puzzle Hunt Guide</title><link>https://xavd.id/blog/post/afterparty-s2-puzzle-guide/</link><description>A complete, consolidated, spoiler-free, step-by-step guide to every puzzle in The Afterparty's second season.&lt;br /&gt;&lt;br /&gt;&lt;a href="https://xavd.id/blog/post/afterparty-s2-puzzle-guide/"&gt;Read the whole thing&lt;/a&gt;.</description><author>The David Brownman Blog</author><pubDate>Sun, 10 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xavd.id/blog/post/afterparty-s2-puzzle-guide/</guid></item><item><title>Wordcount I: Implementing the Unix command-line tool `wc` in modern C++</title><link>https://bytepawn.com/implementing-the-unix-command-line-tool-wc-in-modern-cpp.html</link><description>&lt;p&gt;After reading the excellent book Beautiful C++ about the language's latest features, I implement the Unix command-line tool &lt;code&gt;wc&lt;/code&gt; in modern C++.&lt;br /&gt;&lt;br /&gt; &lt;img alt="Beautiful C++" src="https://m.media-amazon.com/images/I/51ByxdWleSL.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 10 Sep 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/implementing-the-unix-command-line-tool-wc-in-modern-cpp.html</guid></item><item><title>Shared Fantasy: Role-Playing Games as Social Worlds (Review)</title><link>https://ztoz.blog/posts/shared-fantasy/</link><description>&lt;p&gt;&lt;em&gt;Shared Fantasy&lt;/em&gt; is an ethnographic study of fantasy roleplayers in the Minnesota area from 1977 to 1979 by Gary Alan Fine. As this predates the moral panic of the early 1980s (James Egbert disappeared in 1979; the movie &lt;em&gt;&lt;a href="https://www.imdb.com/title/tt0084314/"&gt;Mazes and Monsters&lt;/a&gt;&lt;/em&gt; was released in 1982) and the resultant explosion in popularity of the field, it also serves as a historical artifact of the hobby&amp;rsquo;s early days. Does this book provide a better historical understanding of roleplaying games? Will reading this book make you a better player? My answers are yes and no, respectively.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Fine GA. &lt;em&gt;Shared Fantasy : Role-Playing Games As Social Worlds&lt;/em&gt;. Chicago: University of Chicago Press; 1983.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As an ethnographic study, Fine played in a large number of roleplaying sessions across multiple game systems (Dungeons &amp;amp; Dragons, Chivarly &amp;amp; Sorcery, Traveller, and Empire of the Petal Throne), gamemastered a few sessions, and interviewed players and prominent game designers. He spent most of his time at the Golden Brigade clubhouse, but also played in private games and participated in two conventions. Within the book, he covers group dynamics, individual motivations to play, how players approach their characters, and how players and gamemasters approach their collective, shared worlds.&lt;/p&gt;
&lt;p&gt;Fine separates players into two categories. The first plays a character that is largely an extension of themselves and attempts to &amp;ldquo;win&amp;rdquo;. The second type plays a character, and the player tries to play the character accurately, even if the character choices would disagree with the player&amp;rsquo;s choices. Fine notes that the players in both cases have no trouble distinguishing reality from the game. Historically, Fine is witnessing the growing sophistication and tailoring of content to different styles of play, as Chivalry &amp;amp; Sorcery catered to fans who wanted a more &amp;ldquo;accurate&amp;rdquo; medieval simulation and Empire catered to those interested in exploring a far more alien world with non-traditonal influences. However, contemporary gaming literature was documenting more nuanced categories of motivations, as the literature &lt;a href="https://tvtropes.org/pmwiki/pmwiki.php/Main/PlayerArchetypes"&gt;continues to do today.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Based on contemporary reviews, Fine&amp;rsquo;s accounts of the players militarism aroused the most interest. While he noted that &lt;em&gt;characters&lt;/em&gt; often killed, tortured, and committed sexual violence without remorse, the &lt;em&gt;players&lt;/em&gt; were law-abiding and sometimes pacifists in real-life. (Today, this character behavior would earn the ephithet &lt;a href="https://old.reddit.com/r/DnD/comments/6asjyq/murder_hobos_what_are_they/"&gt;murderhobo&lt;/a&gt;.) In the methodological appendix, the author notes his discomfort at their actions in the game and their crude table talk, but that he played along to avoid seeming weird. A contemporary review criticizes the author for missing an opportunity to do a psychological study of the participants (versus just interviewing them and asking why they did such-and-such). From a gaming perspective, it is unclear if there were any consequences for the character&amp;rsquo;s actions to dissuade them from being sociopathic. Based on my experiences, what he describes is more players testing boundaries than a cultural practice. As evidence, when younger children are at the table, or the table has more mixed sex representation, the players self-censure to be less violent and more chaste.&lt;/p&gt;
&lt;p&gt;As dictated by sociological models, the author describes status structures within the group, which he bases on experience, role, and age. Players with more experience (e.g. sessions) in the game are afforded more respect, have more influence in determining the group&amp;rsquo;s actions, and can select to play roles more to their liking. Gamemasters are afforded the most respect. I do not think this analysis was particularly useful, but it was likely necessary for publication. A contemporary reviewer simulataneously criticized the book for including some content to justify the scholarly nature, while also not including analyses per  several psychological and sociological models&amp;hellip; so it seems he can&amp;rsquo;t win.&lt;/p&gt;
&lt;p&gt;This excess of methodology can be seen in the &amp;ldquo;babysitting crisis&amp;rdquo;. After the club is mentioned in the local newspaper, the gaming club saw parents dumping children at the club to play. Older members and the leadership saw this as an imposition &amp;mdash; that they were being used for free babysitting. The leaders adopted a policy that they would not allow the kids to stay later than the city&amp;rsquo;s curfew (10 pm) and, although it seems they never had to enforce the policy, they reduced the number of kids through the mere threat.&lt;/p&gt;
&lt;p&gt;The book analyzes this situation in terms of power and status, where the older, higher status members used their privilege to enact ageist policies. However, as any person who has tried to teach young children rules to a game has learned, maturity matters. Immature or bad players can easily ruin the game for the entire group. In fact, this was a recent thread in &lt;a href="https://kenzerco.com/knights-of-the-dinner-table/"&gt;&lt;em&gt;Knights of the Dinner Table&lt;/em&gt;&lt;/a&gt; where the players were concerned about including a younger player into a tense, high-stakes scenario. The player concerns were not around abstract notions of status, but practical concerns about maturity, knowledge of the rules, and gaming late into the night. By trying to examine everything from a sociological perspective, the analysis sometimes misconstrues more practical reality.&lt;/p&gt;
&lt;p&gt;While the book contains many interesting anecdotes and is one of the deeper looks into the hobby at the time from the view of a semi-detached player, the contemporary gaming literature at the time was far ahead in terms of understanding players and sharing concrete advise on how to evolve and improve games.&lt;/p&gt;</description><author>ℤ→ℤ</author><pubDate>Sun, 10 Sep 2023 00:59:21 GMT</pubDate><guid isPermaLink="true">https://ztoz.blog/posts/shared-fantasy/</guid></item><item><title>SupaKit</title><link>https://thatxliner.github.io/posts/supakit/</link><description>My ideal full-stack tech stack</description><author>ThatXliner's Blog</author><pubDate>Sat, 09 Sep 2023 20:53:02 GMT</pubDate><guid isPermaLink="true">https://thatxliner.github.io/posts/supakit/</guid></item><item><title>My $500 Developer Laptop</title><link>https://www.mikekasberg.com/blog/2023/09/09/my-500-developer-laptop.html</link><description>&lt;p&gt;I’m not the first person to write about a $500 developer laptop. In fact, I was
inspired by Max Rozen’s &lt;a href="https://maxrozen.com/replacing-my-macbook-m1-with-thinkpad-t480"&gt;Replacing my MacBook Air M1 with a ThinkPad
T480&lt;/a&gt; and
&lt;a href="https://maxrozen.com/getting-your-own-good-enough-laptop-for-under-500"&gt;Getting your own good enough laptop for under
$500&lt;/a&gt;.
Like Max, I’m not only writing a blog about this – I’m actually using the $500
laptop I’m writing about as my personal daily driver. You don’t need a $2,000
computer to have a great machine for web development! The laptop I chose is a
great alternative to the ThinkPad T480. (ThinkPads are great, but they’re not
the only way to get an incredibly capable and pragmatic laptop on a budget.)
I’ve been a fan of Dell for many years, and my $500 developer laptop is a
&lt;strong&gt;Latitude 7490&lt;/strong&gt;. I recently bought one to replace the Latitude e7450 I was
using. Let’s see how it stacks up to the T480, and how well it works as a daily
driver.&lt;/p&gt;

&lt;div class="warning"&gt;
&lt;p&gt;Related posts about finding the perfect computer:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;2019-05-03: &lt;a href="https://www.mikekasberg.com/blog/2019/05/03/computer-shopping-the-ultimate-developer-laptop.html"&gt;Computer Shopping: The Ultimate Developer Laptop&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;2020-05-24: &lt;a href="https://www.mikekasberg.com/blog/2020/05/24/why-i-love-ubuntu-as-a-desktop-os.html"&gt;Why I Love Ubuntu As a Desktop OS&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;2021-08-12: &lt;a href="https://www.mikekasberg.com/blog/2021/08/12/dell-latitudes-are-great-laptops.html"&gt;Dell Latitudes are Great Laptops (and they run Ubuntu well)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;2022-05-07: &lt;a href="https://www.mikekasberg.com/blog/2022/05/07/the-best-computer-you-can-buy-for-100.html"&gt;The Best Computer You Can Buy For $100&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;

&lt;h1 id="the-laptop"&gt;The Laptop&lt;/h1&gt;

&lt;p&gt;Like the ThinkPad T480, the Latitude 7490 is a business class laptop that’s
several years old. (The xx90 indicates it came out in 2019.) That means there
are tons of them on the used marked, they’re easy to work on, and replacement
parts are easy to find. You can buy a 7490 with a wide range of specs; let’s
look at the one I found:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Intel i7 8650U CPU&lt;/li&gt;
  &lt;li&gt;32GB RAM&lt;/li&gt;
  &lt;li&gt;1920x1080 screen&lt;/li&gt;
  &lt;li&gt;60 Wh battery&lt;/li&gt;
  &lt;li&gt;Windows 11 Pro installed (to be replaced with Ubuntu Linux)&lt;/li&gt;
  &lt;li&gt;1TB NVMe SSD&lt;/li&gt;
  &lt;li&gt;WiFi: Intel Dual Band Wireless-AC 8265&lt;/li&gt;
  &lt;li&gt;1280x720 webcam&lt;/li&gt;
  &lt;li&gt;Ports:
    &lt;ul&gt;
      &lt;li&gt;USB-C / Thunderbolt 3&lt;/li&gt;
      &lt;li&gt;HDMI&lt;/li&gt;
      &lt;li&gt;3x USB 3.1&lt;/li&gt;
      &lt;li&gt;Gigabit Ethernet&lt;/li&gt;
      &lt;li&gt;SD Card Reader&lt;/li&gt;
      &lt;li&gt;3.5mm Headset jack&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I originally found the laptop spec’ed out above listed for $436 (including
shipping) on
&lt;a href="https://www.newegg.com/p/pl?N=100006740&amp;amp;SrchInDesc=7490%2CLatitude"&gt;Newegg&lt;/a&gt;,
but after I ordered it the seller contacted me because the 1TB disks were out of
stock. I accepted their offer of a lower price with a smaller hard drive, and
upgraded to a 1TB NVMe drive myself with the savings!&lt;/p&gt;

&lt;p&gt;&lt;img alt="Installing a new SSD in my Latitude 7490" src="https://www.mikekasberg.com/images/my-500-developer-laptop/ssd.jpg" /&gt;&lt;/p&gt;

&lt;p&gt;The laptop was refurbished, so all I received is the laptop and a charger in a
plain box with some bubble wrap. There were a few minor scuffs on the laptop. No
big deal, it works great!&lt;/p&gt;

&lt;p&gt;&lt;img alt="Unwrapping my Latitude 7490" src="https://www.mikekasberg.com/images/my-500-developer-laptop/packaging.jpg" /&gt;&lt;/p&gt;

&lt;p&gt;Compared to the
&lt;a href="https://maxrozen.com/replacing-my-macbook-m1-with-thinkpad-t480"&gt;T480&lt;/a&gt;, I think
this latitude is perhaps &lt;em&gt;slightly better&lt;/em&gt;?!? The processor is slightly faster,
the SSD is larger, and I didn’t need to upgrade the display or RAM. The T480
comes out way ahead on battery life though, with 92 Wh compared to the
Latitude’s 60 Wh. Overall, these are actually very similar machines, and the
main point is you can find either of them very cheaply – a really good computer
(that runs Linux well) for less than $500.&lt;/p&gt;

&lt;h2 id="is-this-really-a-good-computer"&gt;Is this really a good computer?&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Yes, I think the Latitude 7490 really is a good computer&lt;/strong&gt;, and I really enjoy
using it. I prefer Ubuntu Linux over macOS or Windows. After using Linux for a
long time, &lt;a href="https://www.mikekasberg.com/blog/2020/05/24/why-i-love-ubuntu-as-a-desktop-os.html"&gt;Windows feels like a slow, bloated joke to me&lt;/a&gt;, with advertisements plastered
all over the Start menu. macOS is much better, but still feels bloated compared
to Ubuntu. I use vanilla Gnome 3, and it provides a simple, unobtrusive
interface that gets out of my way and lets me get work done. Dell Latitudes run
Ubuntu better than any other computer I’ve ever used, and Ubuntu on a 7490 is a
prefect fit for me.&lt;/p&gt;

&lt;p&gt;I use an M1 Max at work, and I actually prefer my personal Latitude
7490 for most tasks. It’s technically slower, but it’s not &lt;em&gt;noticeably&lt;/em&gt; slower.
To say that a little differently, my 7490 doesn’t feel any slower for most tasks
than the fastest laptop money can buy! The battery lasts a long time. And it
comes with the perfect variety of ports (USB-C, USB-A, HDMI, headset).&lt;/p&gt;

&lt;p&gt;Back in 2019 I wrote about &lt;a href="https://www.mikekasberg.com/blog/2019/05/03/computer-shopping-the-ultimate-developer-laptop.html"&gt;The Ultimate Developer Laptop&lt;/a&gt;. That blog post
was primarily about the Dell Precision 5510 I’d just bought, and why I chose it.
I still stand by that post – the Precision 5510 is a great laptop – but I’ve
found that a few of my preferences have shifted slightly since I wrote it.  I’ve
come to prefer slightly smaller laptops with longer battery life. Back in 2019,
I most often worked at my home desk and rarely used battery power, so the
Precision 5510’s meager battery (I only get 1-2 hours) didn’t bother me much.
Since then, I got married, moved to a bigger house, and now spend much more time
using my laptop away from my desk – whether working outside on my deck or
browsing the web from my couch. I found myself frustrated by the 5510’s short
battery life and relatively heavy weight. In comparison, the Latitude sips power
because it doesn’t have a graphics card, and has a battery that lasts 5+ hours.&lt;/p&gt;

&lt;p&gt;In 2021, I wrote about how well &lt;a href="https://www.mikekasberg.com/blog/2021/08/12/dell-latitudes-are-great-laptops.html"&gt;Dell Latitudes run Linux&lt;/a&gt;, and that was actually the
catalyst that set me on the path toward the 7490 I just bought. I wrote that
2021 post from my own Latitude e7450. I originally bought it as a spare computer
(and a lighter one for travel) with 8GB RAM and a 256GB SSD, but I often
found myself choosing to use it over my Precision 5510 despite the meager specs
because of its small size and longer battery life. I spent like $40 to upgrade
the RAM and disk, I began using my e7450 almost exclusively as my personal daily
driver. To be clear, I’m talking about an e7450 with an i7-5600U, 500GB SSD, and
16GB RAM. The CPU is from 2015, the SSD uses SATA (relatively
slow), and it has integrated graphics.  The fact that I was happier using that
computer than my Precision 5510 just further drives home the point that you can
get a “good enough” computer for dirt cheap! (You can buy a similar e7450 for
under $200 today.)&lt;/p&gt;

&lt;p&gt;Is my $500 Latitude 7490 the perfect web developer machine? Probably not, but I
think it’s darn close. I’d love to have an M1 MacBook Air that runs Ubuntu,
though the keyboard layout would probably drive me nuts. &lt;a href="https://asahilinux.org/"&gt;Asahi
Linux&lt;/a&gt; appears to be usable for some people, but I’m
not sure it’s stable enough to be my own primary machine yet. The &lt;a href="https://system76.com/laptops/lemur"&gt;System76 Lemur
Pro&lt;/a&gt; looks like a great option with a 73Wh
battery, and is probably what I’d buy with an unlimited budget, but it’s hard
for me to justify that much difference in price for minor performance
improvements. The M1 or the latest Intel processor might make sense for an
enterprise codebase that takes 20 minutes to compile, but probably doesn’t make
a ton of difference for the average web developer (or any other computer user).&lt;/p&gt;

&lt;div class="message"&gt;
&lt;p&gt;&lt;b&gt;Battery Life Update (Nov 2023)&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;When I originally bought this laptop, I wasn't completely sure how good the battery life would be. It depends not only on the size of the battery (60 Wh), but also the power consumption of the CPU, display, and other components. After two months of use, I can say I'm very pleased! If I charge the laptop overnight, it can go a full weekend day (6+ hours active use, sleeping in between) without charging and still have some battery left over. I'd guess I could get 8 hours normal use on a full charge!&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;Long story short: I really like my new (used) Dell Latitude 7490. It runs Ubuntu
flawlessly, it has great battery life, it has a great keyboard, it’s
lightweight, and it’s easy and cheap to upgrade and repair. I recommend both
ThinkPads and Latitudes to anyone who wants a great deal on a Linux computer
that they’ll probably fall in love with. Want to shop for your own? See my &lt;a href="https://www.mikekasberg.com/blog/2021/08/12/dell-latitudes-are-great-laptops.html"&gt;Dell
Latitudes are Greal Laptops&lt;/a&gt; post for some tips about what to
look for and where to find them.&lt;/p&gt;</description><author>Mike Kasberg's Blog</author><pubDate>Sat, 09 Sep 2023 16:30:00 GMT</pubDate><guid isPermaLink="true">https://www.mikekasberg.com/blog/2023/09/09/my-500-developer-laptop.html</guid></item><item><title>Home Recording Links</title><link>https://brett.coulstock.id.au/home-recording-links.html</link><description>Resources for Home Recording of Audiobooks and Audio-Description</description><author>Brett Coulstock - Writer</author><pubDate>Sat, 09 Sep 2023 12:00:00 GMT</pubDate><guid isPermaLink="true">https://brett.coulstock.id.au/home-recording-links.html</guid></item><item><title>Another blog</title><link>https://bfontaine.net/2023/09/09/another-blog/</link><description>&lt;p&gt;In 2023 I wrote only one article on this blog, but a few more on the &lt;a href="https://tech.bixoto.com/"&gt;tech blog&lt;/a&gt; of the
company I currently work for, Bixoto:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://tech.bixoto.com/2023/03/20/deploy-mattermost-on-dokku.html"&gt;Deploy Mattermost on Dokku&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://tech.bixoto.com/2023/04/26/add-lets-encrypt-ssl-certificates-on-private-domains-with-dokku.html"&gt;Add Let’s Encrypt SSL Certificates on Private Domains with Dokku&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://tech.bixoto.com/2023/07/10/always-encode-your-requests-payloads-in-python.html"&gt;Always encode your Requests payloads in Python&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://tech.bixoto.com/2023/08/28/install-avifenc-on-ubuntu-20-04.html"&gt;Install avifenc on Ubuntu 20.04&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://tech.bixoto.com/2023/09/04/how-does-bixoto.com-work.html"&gt;How does Bixoto.com work?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This explains why the present blog might look abandoned. It’s not, it’s just that over the years I’ve reduced the time I
spend on side projects and now 95% of my time doing technical stuff on a computer is during work hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;March 2025 update&lt;/strong&gt;: more posts:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://tech.bixoto.com/monitor-anything-with-telegraf-influxdb-grafana-and-python/"&gt;Monitor anything with Telegraf, InfluxDB, Grafana and Python&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://tech.bixoto.com/remove-the-expiry-date-on-self-hosted-gitlab-personal-access-tokens/"&gt;Remove the expiry date on self-hosted Gitlab personal access tokens&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://tech.bixoto.com/how-we-eliminated-almost-all-the-cumulative-layout-shift-cls-from-our-website/"&gt;How we eliminated almost all the Cumulative Layout Shift (CLS) from our website&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://tech.bixoto.com/an-obscure-intermitent-ssh-connection-issue-with-coolify/"&gt;An obscure intermitent SSH connection issue with Coolify&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;January 2026 update&lt;/strong&gt;: even more posts:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://tech.bixoto.com/mooving-away-from-coolify/"&gt;Mooving away from Coolify&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://tech.bixoto.com/migrating-commands-from-poetry-to-uv/"&gt;Migrating commands from Poetry to uv&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://tech.bixoto.com/proxying-internal-website-traffic-to-a-developers-local-machine-for-faster-iteration/"&gt;Proxying internal website traffic to a developer’s local machine for faster iteration&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://tech.bixoto.com/upgrade-mattermost-enterprise-edition-to-team-edition/"&gt;Upgrade Mattermost Enterprise Edition to Team Edition&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://tech.bixoto.com/debugging-ios-safari-from-linux/"&gt;Debugging iOS Safari from Linux&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Baptiste Fontaine’s Blog</author><pubDate>Sat, 09 Sep 2023 10:18:00 GMT</pubDate><guid isPermaLink="true">https://bfontaine.net/2023/09/09/another-blog/</guid></item><item><title>Glossary of tech phrases</title><link>https://zackproser.com/blog/glossary-of-tech-phrases</link><description>If you're coming from Hacker News, I've learned that I need to spell out at the beginning that this post is partly facetious...</description><author>Zachary Proser</author><pubDate>Sat, 09 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/glossary-of-tech-phrases</guid></item><item><title>My mental model of transducers</title><link>http://blog.danieljanus.pl/transducers/</link><description>&lt;div&gt;&lt;h2 id="intro"&gt;Intro&lt;/h2&gt;&lt;p&gt;I’ve been programming in Clojure for a long time, but I haven’t been using transducers much. I learned to mechanically transform &lt;code&gt;(into [] (map f coll))&lt;/code&gt; to &lt;code&gt;(into [] (map f) coll)&lt;/code&gt; for a slight performance gain, but not much beyond that. Recently, however, I’ve found myself refactoring transducers-based code at work, which prompted me to get back to speed.&lt;/p&gt;&lt;p&gt;I found Eero Helenius’ article &lt;a href="https://dev.solita.fi/2021/10/14/grokking-clojure-transducers.html"&gt;“Grokking Clojure transducers”&lt;/a&gt; a great help in that. To me, it’s much more approachable than the &lt;a href="https://clojure.org/reference/transducers"&gt;official documentation&lt;/a&gt; – in a large part because it shows you how to build transducers from the ground up, and this method of learning profoundly resonates with me. I highly recommend it. However, it’s also useful to have a visual intuition of how transducers work, a mental model that hints at the big picture without zooming into the details too much. In this post, I’d like to share mine and illustrate it with a REPL session. (Spoiler alert: there’s &lt;a href="https://github.com/clojure/core.async"&gt;core.async&lt;/a&gt; ahead, but in low quantities.)&lt;/p&gt;&lt;h2 id="pictures"&gt;Pictures&lt;/h2&gt;&lt;p&gt;Imagine data flowing through a conveyor belt. Say, infinitely repeating integers from 1 to 5:&lt;/p&gt;&lt;img alt="Conveyor belt" src="/img/blog/conveyor-belt.svg" /&gt;
&lt;p&gt;I’m using the abstract term “conveyor belt”, rather than “sequence” or something like this, to avoid associations with any implementation details. Just pieces of data, one after another. These data may be anything; they may flow infinitely or stop at some point; may or may not all exist in memory at the same time. Doesn’t matter. That’s the beauty of transducers: they completely abstract away the implementation of sequentiality.&lt;/p&gt;&lt;p&gt;So, what is a transducer, intuitively? It’s a mechanism for &lt;em&gt;transforming conveyor belts into other conveyor belts&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;For example, &lt;code&gt;(map inc)&lt;/code&gt; is a transducer that says: “take this conveyor belt and produce one where every number is incremented”. Applying it to the above belt yields this one:&lt;/p&gt;&lt;img alt="Conveyor belt, transformed" src="/img/blog/conveyor-belt-2.svg" /&gt;
&lt;p&gt;An important thing about transducers is that they’re &lt;em&gt;composable&lt;/em&gt;. To understand that, imagine further transforming the above belt by removing all the odd numbers. Intuitively, that’s what &lt;code&gt;(remove odd?)&lt;/code&gt; does:&lt;/p&gt;&lt;img alt="Conveyor belt, transformed again" src="/img/blog/conveyor-belt-3.svg" /&gt;
&lt;p&gt;(I’ve left the spacing between boxes the same as before, because it helps me visualise &lt;code&gt;(remove odd?)&lt;/code&gt; better. I imagine an invisible gnome sitting above the belt, watching carefully all the boxes that pass below it, and snatching greedily every one that happens to contain an odd number.)&lt;/p&gt;&lt;p&gt;Composability means that Clojure lets you say &lt;code&gt;(comp (map inc) (remove odd?))&lt;/code&gt; to mean the transducer that transforms the first belt to the third one. By putting together two simple building blocks, we produced a more complex one – that it itself reusable and can be used as another building block in an ever more complex data pipeline.&lt;/p&gt;&lt;p&gt;Notice we &lt;em&gt;still&lt;/em&gt; haven’t said anything about the actual representation of the data, but are already able to model complex processes. We can then apply them to actual data, whether it’s a simple vector-to-vector transformation within the same JVM, or listening to a topic on a Kafka cluster, summarizing the incoming data and sending them to a data warehouse.&lt;/p&gt;&lt;h2 id="code"&gt;Code&lt;/h2&gt;&lt;p&gt;OK, enough handwaving, time for a demo. Let’s fire up a REPL and load core.async (I’m assuming you’ve added it to your dependencies already). I won’t reproduce here the resulting values of expressions we evaluate (they’re mostly &lt;code&gt;nil&lt;/code&gt;s anyway), but I will reproduce output from the REPL (as comments).&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-name"&gt;require&lt;/span&gt; &amp;#x27;[clojure.core.async &lt;span class="hljs-symbol"&gt;:refer&lt;/span&gt; [chan &amp;lt;!! &amp;gt;!! thread close!]])
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Why core.async? Because I find it a great way to implement a conveyor belt that you can play with interactively. This can help you understand how the various Clojure-provided transducers work. For the noncognoscenti: core.async is a Clojure library that allows you to implement concurrent processes that communicate over &lt;em&gt;channels&lt;/em&gt;. By default, that communication is synchronous, meaning that if a process tries to read from a channel, it blocks until another process writes something to that channel.&lt;/p&gt;&lt;p&gt;As it happens, we can pass a transducer to the function that creates channels, &lt;code&gt;chan&lt;/code&gt;. It will put the invisible gnomes to work on values that pass through the channel. So you can view that channel as a conveyor belt!&lt;/p&gt;&lt;p&gt;For easy tinkering, we can do this:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-keyword"&gt;defn&lt;/span&gt; &lt;span class="hljs-title"&gt;transformed-belt&lt;/span&gt; [xf]
  (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;let&lt;/span&gt;&lt;/span&gt; [ch (&lt;span class="hljs-name"&gt;chan&lt;/span&gt; &lt;span class="hljs-number"&gt;1&lt;/span&gt; xf)]
    (&lt;span class="hljs-name"&gt;thread&lt;/span&gt;
      (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;loop&lt;/span&gt;&lt;/span&gt; []
        (&lt;span class="hljs-name"&gt;when-some&lt;/span&gt; [value (&lt;span class="hljs-name"&gt;&amp;lt;!!&lt;/span&gt; ch)]
          (&lt;span class="hljs-name"&gt;println&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;Value:&amp;quot;&lt;/span&gt; (&lt;span class="hljs-name"&gt;pr-str&lt;/span&gt; value)))
          (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;recur&lt;/span&gt;&lt;/span&gt;)))
    ch))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This fires up a process working at the receiving end of the conveyor belt. It will print out any transformed values as soon as they become available. Typing at the REPL, we will assume the role of producer, putting data on the belt.&lt;/p&gt;&lt;p&gt;Like this:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-keyword"&gt;def&lt;/span&gt; &lt;span class="hljs-title"&gt;b&lt;/span&gt; (&lt;span class="hljs-name"&gt;transformed-belt&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;map&lt;/span&gt;&lt;/span&gt; inc)))
(&lt;span class="hljs-name"&gt;&amp;gt;!!&lt;/span&gt; b &lt;span class="hljs-number"&gt;2&lt;/span&gt;)
&lt;span class="hljs-comment"&gt;; Value: 3&lt;/span&gt;
(&lt;span class="hljs-name"&gt;&amp;gt;!!&lt;/span&gt; b &lt;span class="hljs-number"&gt;42&lt;/span&gt;)
&lt;span class="hljs-comment"&gt;; Value: 43&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It works! We’re putting in numbers, and out come the incremented ones.&lt;/p&gt;&lt;p&gt;When we’re done experimenting with the belt, we need to &lt;code&gt;close!&lt;/code&gt; it. This will cause the worker thread to shutdown.&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-name"&gt;close!&lt;/span&gt; b)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can now experiment with something more complex, like that combined transducer we’ve talked about before:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-keyword"&gt;def&lt;/span&gt; &lt;span class="hljs-title"&gt;b&lt;/span&gt; (&lt;span class="hljs-name"&gt;transformed-belt&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;comp&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;map&lt;/span&gt;&lt;/span&gt; inc) (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;remove&lt;/span&gt;&lt;/span&gt; odd?))))
(&lt;span class="hljs-name"&gt;&amp;gt;!!&lt;/span&gt; b &lt;span class="hljs-number"&gt;1&lt;/span&gt;)
&lt;span class="hljs-comment"&gt;; Value: 2&lt;/span&gt;
(&lt;span class="hljs-name"&gt;&amp;gt;!!&lt;/span&gt; b &lt;span class="hljs-number"&gt;2&lt;/span&gt;)
(&lt;span class="hljs-name"&gt;&amp;gt;!!&lt;/span&gt; b &lt;span class="hljs-number"&gt;3&lt;/span&gt;)
&lt;span class="hljs-comment"&gt;; Value: 4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We got the transformed 1 and 3, but the intermediate value for 2 was odd, so it was snatched by the gnome and we never saw it.&lt;/p&gt;&lt;p&gt;There’s even more fun to be had! Let’s try &lt;code&gt;(partition-all 3)&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-name"&gt;close!&lt;/span&gt; b)
(&lt;span class="hljs-keyword"&gt;def&lt;/span&gt; &lt;span class="hljs-title"&gt;b&lt;/span&gt; (&lt;span class="hljs-name"&gt;transformed-belt&lt;/span&gt; (&lt;span class="hljs-name"&gt;partition-all&lt;/span&gt; &lt;span class="hljs-number"&gt;3&lt;/span&gt;)))
(&lt;span class="hljs-name"&gt;&amp;gt;!!&lt;/span&gt; b &lt;span class="hljs-number"&gt;1&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Nothing…&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-name"&gt;&amp;gt;!!&lt;/span&gt; b &lt;span class="hljs-number"&gt;2&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Still nothing…&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-name"&gt;&amp;gt;!!&lt;/span&gt; b &lt;span class="hljs-number"&gt;3&lt;/span&gt;)
&lt;span class="hljs-comment"&gt;; Value: [1 2 3]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Blammo! Our gnome is now packaging together incoming items into bundles of three, caching them in the interim while the bundle is not complete yet. But if we close the input prematurely, it will acknowledge and produce the incomplete bundle:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-name"&gt;&amp;gt;!!&lt;/span&gt; b &lt;span class="hljs-number"&gt;4&lt;/span&gt;)
(&lt;span class="hljs-name"&gt;&amp;gt;!!&lt;/span&gt; b &lt;span class="hljs-number"&gt;5&lt;/span&gt;)
(&lt;span class="hljs-name"&gt;close!&lt;/span&gt; b)
&lt;span class="hljs-comment"&gt;; Value: [4 5]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In fact, &lt;code&gt;partition-all&lt;/code&gt; is what prompted me to write this post. That code at work I mentioned actually included a transducer composition that had a &lt;code&gt;(net.cgrand.xforms/into [])&lt;/code&gt; in it. That transducer (from Christophe Grand’s &lt;a href="https://github.com/cgrand/xforms/"&gt;xforms&lt;/a&gt; library) accumulates data until there’s nothing more to accumulate, and then emits all of it as one large vector. By replacing it with &lt;code&gt;partition-all&lt;/code&gt;, I altered the downstream processing to handle multiple smaller batches rather than one huge batch, improving the system’s latency.&lt;/p&gt;&lt;p&gt;A small change for a huge win. Clojure continues to amaze me.&lt;/p&gt;&lt;p&gt;Plus, it’s fun to make JS-less animations in SVG. :)&lt;/p&gt;&lt;/div&gt;</description><author>code · words · emotions: Daniel Janus’s blog</author><pubDate>Sat, 09 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">http://blog.danieljanus.pl/transducers/</guid></item><item><title>Displaying the right Vercel deployment URLs in Next.js</title><link>https://francoisbest.com/posts/2023/displaying-the-right-vercel-deployment-urls-in-nextjs?utm_source=rss</link><description>A TIL about caching, Git branch management and React Server Components.</description><author>Articles by François Best</author><pubDate>Sat, 09 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://francoisbest.com/posts/2023/displaying-the-right-vercel-deployment-urls-in-nextjs?utm_source=rss</guid></item><item><title>Folklore Night Sky: Overcoming Ancestry</title><link>https://jdsalaro.com/poem/folklore-night-sky-overcoming-ancestry/</link><description>This poem was originally written for KAMINA’s1 act at the Heidelberger Literaturherbst 20232.</description><author>Jayson Salazar Rodriguez | @jdsalaro | Blog</author><pubDate>Sat, 09 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jdsalaro.com/poem/folklore-night-sky-overcoming-ancestry/</guid></item><item><title>The Photo Game: Old</title><link>https://andrewbridges.org/the-photo-game-old/</link><description>This week, I'm submitting a photograph of my first digital camera, an old, inexpensive DSLR. It's a Canon EOS Rebel T5 with an 18-55mm kit lens.</description><author>Show Notes</author><pubDate>Fri, 08 Sep 2023 22:17:06 GMT</pubDate><guid isPermaLink="true">https://andrewbridges.org/the-photo-game-old/</guid></item><item><title>syslog for fun (and profit?) – olf 2023 talk</title><link>https://antipaucity.com/2023/09/08/syslog-for-fun-and-profit-olf-2023-talk/</link><description>I had the privilege of speaking at this year&amp;#8217;s Ohio Linux Fest on the topic of syslog Slidedeck PDF &amp;#8211; https://antipaucity.com/olf2023-syslog.pdf If you would like me to give this, or any other, talk for your organization, please contact me via LinkedIn or Twitter You can watch the talk from OLF&amp;#8217;s live stream of the event...&lt;p class="more-link-wrap"&gt;&lt;a class="more-link" href="https://antipaucity.com/2023/09/08/syslog-for-fun-and-profit-olf-2023-talk/"&gt;continue&lt;span class="screen-reader-text"&gt; &amp;#8220;syslog for fun (and profit?) &amp;#8211; olf 2023 talk&amp;#8221;&lt;/span&gt; &amp;#187;&lt;/a&gt;&lt;/p&gt;</description><author>antipaucity</author><pubDate>Fri, 08 Sep 2023 21:47:25 GMT</pubDate><guid isPermaLink="true">https://antipaucity.com/2023/09/08/syslog-for-fun-and-profit-olf-2023-talk/</guid></item><item><title>Suggested readings week 36/2023</title><link>https://www.stefanocosta.org/suggested-readings-week-36-2023/</link><description>&lt;hr /&gt;&lt;h3 id="computing-history-at-bell-labs"&gt;&lt;a href="https://research.swtch.com/bell-labs?ref=stefanocosta.org" rel="nofollow noreferrer noopener"&gt;Computing History at Bell Labs&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;No need to explain what Bell Laboratories have been for the advancement of humanity in terms of technological discoveries and use cases perfection. Most of the published stuff from the ancient years at the Bell Labs has gone from the easy of reach of its&lt;/p&gt;</description><author>Food for Thought (by Stefano Costa)</author><pubDate>Fri, 08 Sep 2023 15:51:08 GMT</pubDate><guid isPermaLink="true">https://www.stefanocosta.org/suggested-readings-week-36-2023/</guid></item><item><title>Wilesco steam fire engine D305</title><link>https://www.xythobuz.de/2023_09_08_wilesco.html</link><description>&lt;p&gt;Somewhere around twenty years ago, when I was still a small child, I got a &lt;a href="https://www.wilesco-shop.de/mobile-dampfmaschinen/dampf-feuerwehrauto-d305.html"&gt;Wilesco D305 steam fire engine&lt;/a&gt; for christmas.
It's now finally back in my possession, so of course I had to try it out.
My friend Igor and his daugther volunteered to help 🚂&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/wilesco_feuerwehr_1.jpg"&gt;&lt;img alt="D305 in front of Toolbox Markdorf" class="pic" src="https://www.xythobuz.de/img/wilesco_feuerwehr_1_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Unfortunately it didn't work too well.
We got it to actually drive for about 30sec, but that was pretty much the best we could make it do.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/wilesco_feuerwehr_v_2.mp4"&gt;&lt;img alt="Machine startup and run without load." class="pic" src="https://www.xythobuz.de/img/wilesco_feuerwehr_v_2_thumb.png" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/wilesco_feuerwehr_v_4.mp4"&gt;&lt;img alt="Starting the machine." class="pic" src="https://www.xythobuz.de/img/wilesco_feuerwehr_v_4_thumb.png" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/wilesco_feuerwehr_v_5.mp4"&gt;&lt;img alt="Driving with 'remote control'." class="pic" src="https://www.xythobuz.de/img/wilesco_feuerwehr_v_5_thumb.png" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;I think it's a combination of negative factors:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The main shaft of the flywheel is slightly bent, causing increased resistance to turning as well as misalignment of gears.&lt;/li&gt;
&lt;li&gt;The piston and cylinder are not moving totally freely, even after oiling everything.&lt;/li&gt;
&lt;li&gt;The main valve is leaking and very wobbly.&lt;/li&gt;
&lt;li&gt;The safety valve o-ring is worn out and loose.&lt;/li&gt;
&lt;li&gt;The whistle is leaking.&lt;/li&gt;
&lt;li&gt;The whole kettle is full of scale and probably oxidized.
Lots of green-white slug came out of the outlet pipe at first.&lt;/li&gt;
&lt;li&gt;The fuel is probably not new and may have sucked in some moisture from the air, reducing the intensity of the fire.&lt;/li&gt;
&lt;li&gt;A part of the chimney is missing.
Maybe this results in insuficcient airflow to the fire?
The fire seemed too weak after inserting it below the kettle, burning more intensely outside the firebox.&lt;/li&gt;
&lt;li&gt;Maybe the soot from past firings on the kettle is reducing thermal conduction?&lt;/li&gt;
&lt;li&gt;The gears are not properly touching when trying to drive the pump.&lt;/li&gt;
&lt;li&gt;The pump is leaking and does not produce any pressure or flow.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So not much steam is produced in the first place.
Then some of it is lost in the leaking valves.
And finally the machine itself is hard to move.
So even with the most intense fire we managed to create there's really not much pressure / steam produced.
And even in the short time where the whistle sounded like there was good pressure, it still was not enough to run it properly.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/wilesco_feuerwehr_v_3.mp4"&gt;&lt;img alt="Machine moving for the first time." class="pic" src="https://www.xythobuz.de/img/wilesco_feuerwehr_v_3_thumb.png" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/wilesco_feuerwehr_v_1.mp4"&gt;&lt;img alt="Machine running, not driving any load." class="pic" src="https://www.xythobuz.de/img/wilesco_feuerwehr_v_1_thumb.png" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Fortunately Wilesco still exists.
And as you would expect from a small and old company like this, they also have a &lt;a href="https://www.wilesco-shop.de/search?search=d305"&gt;big set of replacement parts&lt;/a&gt;.
Also my grandfather was actually a "licensed" Wilesco repairman and seller.
So he had quite a big collection of their machines, toys and replacement parts.
Some replacement parts actually were part of the things I managed to get from the house last month.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/wilesco_feuerwehr_3.jpg"&gt;&lt;img alt="Assortment of Wilesco spare parts" class="pic" src="https://www.xythobuz.de/img/wilesco_feuerwehr_3_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/wilesco_feuerwehr_2.jpg"&gt;&lt;img alt="More Wilesco spare parts" class="pic" src="https://www.xythobuz.de/img/wilesco_feuerwehr_2_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;I have a bunch of generic parts like gaskets, but unfortunately no complete safety valve with a matching thread.
And also I don't yet have the required brazing/soldering skills to do work like replacing the main valve.
So I'm not quite sure how to proceed with this at the moment.
For now it will have to go to the healing bench.&lt;/p&gt;</description><author>xythobuz.de Blog</author><pubDate>Fri, 08 Sep 2023 15:00:00 GMT</pubDate><guid isPermaLink="true">https://www.xythobuz.de/2023_09_08_wilesco.html</guid></item><item><title>A Bit of SaaS Weekly: Heat Wave</title><link>https://ethanmick.com/a-bit-of-saas-weekly-heat-wave/</link><description>&lt;p&gt;This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by &lt;a href="https://ethanmick.com/"&gt;Ethan Mick&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;The end of summer has come with a vicious heat wave in the northeast US. When it&amp;apos;s this hot, all I can think of is how nice it&lt;/p&gt;</description><author>Build SaaS with Ethan</author><pubDate>Fri, 08 Sep 2023 13:00:52 GMT</pubDate><guid isPermaLink="true">https://ethanmick.com/a-bit-of-saas-weekly-heat-wave/</guid></item><item><title>My Tribe of Builders Interview</title><link>https://andinfinity.eu/post/tribe-builders-interview/</link><description>&lt;p&gt;I did my first &amp;ldquo;interview&amp;rdquo; with Mat from &lt;a href="https://tribeof.builders/"&gt;Tribe of Builders&lt;/a&gt; and it was an interesting experience. He provided me with a list of questions and I could choose the ones I wanted to answer most. I got so into it that I answered quite a lot. After editing I could review the post, but it was good as it was. It was an interesting experience and I really like the outcome. Be sure to give Mat some love, he is gathering advice from builders all over the world and it is a great resource for anyone who wants to start building something or needs some motivation.&lt;/p&gt;</description><author>andinfinity</author><pubDate>Fri, 08 Sep 2023 10:37:24 GMT</pubDate><guid isPermaLink="true">https://andinfinity.eu/post/tribe-builders-interview/</guid></item><item><title>Jury Duty: Season 1</title><link>https://olshansky.info/tv/jury_duty_season_1/</link><description>Olshansky's review of Jury Duty: Season 1</description><author>🦉 olshansky 🦁</author><pubDate>Fri, 08 Sep 2023 05:57:34 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/tv/jury_duty_season_1/</guid></item><item><title>System.IO.IOException with a .NET gRPC Project on Mac</title><link>https://whatibroke.com/2023/09/08/system-io-ioexception-with-a-net-grpc-project-on-mac/</link><description>Hey everyone, I created a new gRPC project with Visual Studio on a Mac and ran into the following error when trying to run it: It turns out that Kestrel does not support HTTP/2 with TLS on MacOS. In order to get around it we end up having to configure it to not use TLS. &amp;#8230; &lt;a class="more-link" href="https://whatibroke.com/2023/09/08/system-io-ioexception-with-a-net-grpc-project-on-mac/"&gt;Continue reading&lt;span class="screen-reader-text"&gt; "System.IO.IOException with a .NET gRPC Project on&amp;#160;Mac"&lt;/span&gt;&lt;/a&gt;</description><author>What I Broke – Programming and Web Development</author><pubDate>Fri, 08 Sep 2023 04:02:47 GMT</pubDate><guid isPermaLink="true">https://whatibroke.com/2023/09/08/system-io-ioexception-with-a-net-grpc-project-on-mac/</guid></item><item><title>Exclude files from git revision without adding to .gitignore</title><link>https://gourav.io/blog/exclude-files-from-git</link><description>Exclude files from git revision without adding them to .gitignore</description><author>Gourav Goyal</author><pubDate>Fri, 08 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://gourav.io/blog/exclude-files-from-git</guid></item><item><title>Python profiling and timing utils</title><link>https://ricardoanderegg.com/posts/python-profiling-timing-utils/</link><description>&lt;p&gt;These are just a few Python snippets that I have been using a lot for timing and profiling functions using only the standard library. You can also find the code here as a &lt;a href="https://gist.github.com/polyrand/bb39fb93246ced7464abf52d87fec3a7"&gt;GitHub Gist&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight" style="background: #f8f8f8;"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style="color: #204A87; font-weight: bold;"&gt;import&lt;/span&gt;&lt;span style="color: #F8F8F8;"&gt; &lt;/span&gt;&lt;span style="color: #000;"&gt;contextlib&lt;/span&gt;
&lt;span style="color: #204A87; font-weight: bold;"&gt;from&lt;/span&gt;&lt;span style="color: #F8F8F8;"&gt; &lt;/span&gt;&lt;span style="color: #000;"&gt;functools&lt;/span&gt;&lt;span style="color: #F8F8F8;"&gt; &lt;/span&gt;&lt;span style="color: #204A87; font-weight: bold;"&gt;import&lt;/span&gt; &lt;span style="color: #000;"&gt;wraps&lt;/span&gt;
&lt;span style="color: #204A87; font-weight: bold;"&gt;import&lt;/span&gt;&lt;span style="color: #F8F8F8;"&gt; &lt;/span&gt;&lt;span style="color: #000;"&gt;cProfile&lt;/span&gt;
&lt;span style="color: #204A87; font-weight: bold;"&gt;from&lt;/span&gt;&lt;span style="color: #F8F8F8;"&gt; &lt;/span&gt;&lt;span style="color: #000;"&gt;typing&lt;/span&gt;&lt;span style="color: #F8F8F8;"&gt; &lt;/span&gt;&lt;span style="color: #204A87; font-weight: bold;"&gt;import&lt;/span&gt; &lt;span style="color: #000;"&gt;Optional&lt;/span&gt;
&lt;span style="color: #204A87; font-weight: bold;"&gt;import&lt;/span&gt;&lt;span style="color: #F8F8F8;"&gt; &lt;/span&gt;&lt;span style="color: #000;"&gt;io&lt;/span&gt;
&lt;span style="color: #204A87; font-weight: bold;"&gt;import&lt;/span&gt;&lt;span style="color: #F8F8F8;"&gt; &lt;/span&gt;&lt;span style="color: #000;"&gt;pstats&lt;/span&gt;
&lt;span style="color: #204A87; font-weight: bold;"&gt;import&lt;/span&gt;&lt;span style="color: #F8F8F8;"&gt; &lt;/span&gt;&lt;span style="color: #000;"&gt;time&lt;/span&gt;
&lt;span style="color: #204A87; font-weight: bold;"&gt;from&lt;/span&gt;&lt;span style="color: #F8F8F8;"&gt; &lt;/span&gt;&lt;span style="color: #000;"&gt;pathlib&lt;/span&gt;&lt;span style="color: #F8F8F8;"&gt; &lt;/span&gt;&lt;span style="color: #204A87; font-weight: bold;"&gt;import&lt;/span&gt; &lt;span style="color: #000;"&gt;Path&lt;/span&gt;
&lt;span style="color: #204A87; font-weight: bold;"&gt;from&lt;/span&gt;&lt;span style="color: #F8F8F8;"&gt; &lt;/span&gt;&lt;span style="color: #000;"&gt;functools&lt;/span&gt;&lt;span style="color: #F8F8F8;"&gt; &lt;/span&gt;&lt;span style="color: #204A87; font-weight: bold;"&gt;import&lt;/span&gt; &lt;span style="color: #000;"&gt;wraps&lt;/span&gt;


&lt;span style="color: #5C35CC; font-weight: bold;"&gt;@contextlib&lt;/span&gt;&lt;span style="color: #CE5C00; font-weight: bold;"&gt;.&lt;/span&gt;&lt;span style="color: #000;"&gt;contextmanager&lt;/span&gt;
&lt;span style="color: #204A87; font-weight: bold;"&gt;def&lt;/span&gt;&lt;span style="color: #F8F8F8;"&gt; &lt;/span&gt;&lt;span style="color: #000;"&gt;profile&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #000;"&gt;filename&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;:&lt;/span&gt; &lt;span style="color: #000;"&gt;Path&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;,&lt;/span&gt; &lt;span style="color: #CE5C00; font-weight: bold;"&gt;*&lt;/span&gt;&lt;span style="color: #000;"&gt;args&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;,&lt;/span&gt; &lt;span style="color: #CE5C00; font-weight: bold;"&gt;**&lt;/span&gt;&lt;span style="color: #000;"&gt;kwargs&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;):&lt;/span&gt;
    &lt;span style="color: #000;"&gt;profile&lt;/span&gt; &lt;span style="color: #CE5C00; font-weight: bold;"&gt;=&lt;/span&gt; &lt;span style="color: #000;"&gt;cProfile&lt;/span&gt;&lt;span style="color: #CE5C00; font-weight: bold;"&gt;.&lt;/span&gt;&lt;span style="color: #000;"&gt;Profile&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #CE5C00; font-weight: bold;"&gt;*&lt;/span&gt;&lt;span style="color: #000;"&gt;args&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;,&lt;/span&gt; &lt;span style="color: #CE5C00; font-weight: bold;"&gt;**&lt;/span&gt;&lt;span style="color: #000;"&gt;kwargs&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;)&lt;/span&gt;

    &lt;span style="color: #000;"&gt;profile&lt;/span&gt;&lt;span style="color: #CE5C00; font-weight: bold;"&gt;.&lt;/span&gt;&lt;span style="color: #000;"&gt;enable&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;()&lt;/span&gt;
    &lt;span style="color: #204A87; font-weight: bold;"&gt;yield&lt;/span&gt;
    &lt;span style="color: #000;"&gt;profile&lt;/span&gt;&lt;span style="color: #CE5C00; font-weight: bold;"&gt;.&lt;/span&gt;&lt;span style="color: #000;"&gt;disable&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;()&lt;/span&gt;

    &lt;span style="color: #000;"&gt;s&lt;/span&gt; &lt;span style="color: #CE5C00; font-weight: bold;"&gt;=&lt;/span&gt; &lt;span style="color: #000;"&gt;io&lt;/span&gt;&lt;span style="color: #CE5C00; font-weight: bold;"&gt;.&lt;/span&gt;&lt;span style="color: #000;"&gt;StringIO&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;()&lt;/span&gt;
    &lt;span style="color: #000;"&gt;sortby&lt;/span&gt; &lt;span style="color: #CE5C00; font-weight: bold;"&gt;=&lt;/span&gt; &lt;span style="color: #000;"&gt;pstats&lt;/span&gt;&lt;span style="color: #CE5C00; font-weight: bold;"&gt;.&lt;/span&gt;&lt;span style="color: #000;"&gt;SortKey&lt;/span&gt;&lt;span style="color: #CE5C00; font-weight: bold;"&gt;.&lt;/span&gt;&lt;span style="color: #000;"&gt;CUMULATIVE&lt;/span&gt;
    &lt;span style="color: #000;"&gt;ps&lt;/span&gt; &lt;span style="color: #CE5C00; font-weight: bold;"&gt;=&lt;/span&gt; &lt;span style="color: #000;"&gt;pstats&lt;/span&gt;&lt;span style="color: #CE5C00; font-weight: bold;"&gt;.&lt;/span&gt;&lt;span style="color: #000;"&gt;Stats&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #000;"&gt;profile&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;,&lt;/span&gt; &lt;span style="color: #000;"&gt;stream&lt;/span&gt;&lt;span style="color: #CE5C00; font-weight: bold;"&gt;=&lt;/span&gt;&lt;span style="color: #000;"&gt;s&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;)&lt;/span&gt;&lt;span style="color: #CE5C00; font-weight: bold;"&gt;.&lt;/span&gt;&lt;span style="color: #000;"&gt;strip_dirs&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;()&lt;/span&gt;&lt;span style="color: #CE5C00; font-weight: bold;"&gt;.&lt;/span&gt;&lt;span style="color: #000;"&gt;sort_stats&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #000;"&gt;sortby&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;)&lt;/span&gt;
    &lt;span style="color: #000;"&gt;ps&lt;/span&gt;&lt;span style="color: #CE5C00; font-weight: bold;"&gt;.&lt;/span&gt;&lt;span style="color: #000;"&gt;print_stats&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;()&lt;/span&gt;
    &lt;span style="color: #204A87; font-weight: bold;"&gt;with&lt;/span&gt; &lt;span style="color: #204A87;"&gt;open&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #000;"&gt;filename&lt;/span&gt;&lt;span style="color: #CE5C00; font-weight: bold;"&gt;.&lt;/span&gt;&lt;span style="color: #000;"&gt;with_suffix&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #4E9A06;"&gt;&amp;quot;.txt&amp;quot;&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;),&lt;/span&gt; &lt;span style="color: #4E9A06;"&gt;&amp;quot;w&amp;quot;&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;)&lt;/span&gt; &lt;span style="color: #204A87; font-weight: bold;"&gt;as&lt;/span&gt; &lt;span style="color: #000;"&gt;f&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;:&lt;/span&gt;
        &lt;span style="color: #000;"&gt;f&lt;/span&gt;&lt;span style="color: #CE5C00; font-weight: bold;"&gt;.&lt;/span&gt;&lt;span style="color: #000;"&gt;write&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #000;"&gt;s&lt;/span&gt;&lt;span style="color: #CE5C00; font-weight: bold;"&gt;.&lt;/span&gt;&lt;span style="color: #000;"&gt;getvalue&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;())&lt;/span&gt;

    &lt;span style="color: #000;"&gt;profile&lt;/span&gt;&lt;span style="color: #CE5C00; font-weight: bold;"&gt;.&lt;/span&gt;&lt;span style="color: #000;"&gt;dump_stats&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #000;"&gt;filename&lt;/span&gt;&lt;span style="color: #CE5C00; font-weight: bold;"&gt;.&lt;/span&gt;&lt;span style="color: #000;"&gt;with_suffix&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #4E9A06;"&gt;&amp;quot;.prof&amp;quot;&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;))&lt;/span&gt;


&lt;span style="color: #204A87; font-weight: bold;"&gt;def&lt;/span&gt;&lt;span style="color: #F8F8F8;"&gt; &lt;/span&gt;&lt;span style="color: #000;"&gt;profiled&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #000;"&gt;name&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;:&lt;/span&gt; &lt;span style="color: #000;"&gt;Path&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;):&lt;/span&gt;
    &lt;span style="color: #204A87; font-weight: bold;"&gt;def&lt;/span&gt;&lt;span style="color: #F8F8F8;"&gt; &lt;/span&gt;&lt;span style="color: #000;"&gt;decorator&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #000;"&gt;fn&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;):&lt;/span&gt;
        &lt;span style="color: #5C35CC; font-weight: bold;"&gt;@wraps&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #000;"&gt;fn&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;)&lt;/span&gt;
        &lt;span style="color: #204A87; font-weight: bold;"&gt;def&lt;/span&gt;&lt;span style="color: #F8F8F8;"&gt; &lt;/span&gt;&lt;span style="color: #000;"&gt;wrapper&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #CE5C00; font-weight: bold;"&gt;*&lt;/span&gt;&lt;span style="color: #000;"&gt;args&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;,&lt;/span&gt; &lt;span style="color: #CE5C00; font-weight: bold;"&gt;**&lt;/span&gt;&lt;span style="color: #000;"&gt;kwargs&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;):&lt;/span&gt;
            &lt;span style="color: #204A87; font-weight: bold;"&gt;with&lt;/span&gt; &lt;span style="color: #000;"&gt;profile&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #000;"&gt;name&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;):&lt;/span&gt;
                &lt;span style="color: #204A87; font-weight: bold;"&gt;return&lt;/span&gt; &lt;span style="color: #000;"&gt;fn&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #CE5C00; font-weight: bold;"&gt;*&lt;/span&gt;&lt;span style="color: #000;"&gt;args&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;,&lt;/span&gt; &lt;span style="color: #CE5C00; font-weight: bold;"&gt;**&lt;/span&gt;&lt;span style="color: #000;"&gt;kwargs&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;)&lt;/span&gt;

        &lt;span style="color: #204A87; font-weight: bold;"&gt;return&lt;/span&gt; &lt;span style="color: #000;"&gt;wrapper&lt;/span&gt;

    &lt;span style="color: #204A87; font-weight: bold;"&gt;return&lt;/span&gt; &lt;span style="color: #000;"&gt;decorator&lt;/span&gt;


&lt;span style="color: #5C35CC; font-weight: bold;"&gt;@contextlib&lt;/span&gt;&lt;span style="color: #CE5C00; font-weight: bold;"&gt;.&lt;/span&gt;&lt;span style="color: #000;"&gt;contextmanager&lt;/span&gt;
&lt;span style="color: #204A87; font-weight: bold;"&gt;def&lt;/span&gt;&lt;span style="color: #F8F8F8;"&gt; &lt;/span&gt;&lt;span style="color: #000;"&gt;timeit&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #000;"&gt;output_file&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;:&lt;/span&gt; &lt;span style="color: #000;"&gt;Optional&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;[&lt;/span&gt;&lt;span style="color: #000;"&gt;Path&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;]&lt;/span&gt; &lt;span style="color: #CE5C00; font-weight: bold;"&gt;=&lt;/span&gt; &lt;span style="color: #204A87; font-weight: bold;"&gt;None&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;):&lt;/span&gt;
    &lt;span style="color: #000;"&gt;start&lt;/span&gt; &lt;span style="color: #CE5C00; font-weight: bold;"&gt;=&lt;/span&gt; &lt;span style="color: #000;"&gt;time&lt;/span&gt;&lt;span style="color: #CE5C00; font-weight: bold;"&gt;.&lt;/span&gt;&lt;span style="color: #000;"&gt;perf_counter&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;()&lt;/span&gt;
    &lt;span style="color: #204A87; font-weight: bold;"&gt;yield&lt;/span&gt;
    &lt;span style="color: #000;"&gt;end&lt;/span&gt; &lt;span style="color: #CE5C00; font-weight: bold;"&gt;=&lt;/span&gt; &lt;span style="color: #000;"&gt;time&lt;/span&gt;&lt;span style="color: #CE5C00; font-weight: bold;"&gt;.&lt;/span&gt;&lt;span style="color: #000;"&gt;perf_counter&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;()&lt;/span&gt;
    &lt;span style="color: #204A87; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #000;"&gt;output_file&lt;/span&gt; &lt;span style="color: #204A87; font-weight: bold;"&gt;is&lt;/span&gt; &lt;span style="color: #204A87; font-weight: bold;"&gt;not&lt;/span&gt; &lt;span style="color: #204A87; font-weight: bold;"&gt;None&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;:&lt;/span&gt;
        &lt;span style="color: #204A87; font-weight: bold;"&gt;with&lt;/span&gt; &lt;span style="color: #204A87;"&gt;open&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #000;"&gt;output_file&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;,&lt;/span&gt; &lt;span style="color: #4E9A06;"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;)&lt;/span&gt; &lt;span style="color: #204A87; font-weight: bold;"&gt;as&lt;/span&gt; &lt;span style="color: #000;"&gt;f&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;:&lt;/span&gt;
            &lt;span style="color: #000;"&gt;f&lt;/span&gt;&lt;span style="color: #CE5C00; font-weight: bold;"&gt;.&lt;/span&gt;&lt;span style="color: #000;"&gt;write&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #4E9A06;"&gt;f&amp;quot;{&lt;/span&gt;&lt;span style="color: #000;"&gt;end&lt;/span&gt;&lt;span style="color: #F8F8F8;"&gt; &lt;/span&gt;&lt;span style="color: #CE5C00; font-weight: bold;"&gt;-&lt;/span&gt;&lt;span style="color: #F8F8F8;"&gt; &lt;/span&gt;&lt;span style="color: #000;"&gt;start&lt;/span&gt;&lt;span style="color: #4E9A06;"&gt;}\n&amp;quot;&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;)&lt;/span&gt;
    &lt;span style="color: #204A87; font-weight: bold;"&gt;else&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;:&lt;/span&gt;
        &lt;span style="color: #204A87;"&gt;print&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #4E9A06;"&gt;f&amp;quot;{&lt;/span&gt;&lt;span style="color: #000;"&gt;end&lt;/span&gt;&lt;span style="color: #F8F8F8;"&gt; &lt;/span&gt;&lt;span style="color: #CE5C00; font-weight: bold;"&gt;-&lt;/span&gt;&lt;span style="color: #F8F8F8;"&gt; &lt;/span&gt;&lt;span style="color: #000;"&gt;start&lt;/span&gt;&lt;span style="color: #4E9A06;"&gt;}&amp;quot;&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;)&lt;/span&gt;


&lt;span style="color: #204A87; font-weight: bold;"&gt;def&lt;/span&gt;&lt;span style="color: #F8F8F8;"&gt; &lt;/span&gt;&lt;span style="color: #000;"&gt;timed&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #000;"&gt;output_file&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;:&lt;/span&gt; &lt;span style="color: #000;"&gt;Optional&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;[&lt;/span&gt;&lt;span style="color: #000;"&gt;Path&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;]&lt;/span&gt; &lt;span style="color: #CE5C00; font-weight: bold;"&gt;=&lt;/span&gt; &lt;span style="color: #204A87; font-weight: bold;"&gt;None&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;):&lt;/span&gt;
    &lt;span style="color: #204A87; font-weight: bold;"&gt;def&lt;/span&gt;&lt;span style="color: #F8F8F8;"&gt; &lt;/span&gt;&lt;span style="color: #000;"&gt;decorator&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #000;"&gt;fn&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;):&lt;/span&gt;
        &lt;span style="color: #5C35CC; font-weight: bold;"&gt;@wraps&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #000;"&gt;fn&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;)&lt;/span&gt;
        &lt;span style="color: #204A87; font-weight: bold;"&gt;def&lt;/span&gt;&lt;span style="color: #F8F8F8;"&gt; &lt;/span&gt;&lt;span style="color: #000;"&gt;wrapper&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #CE5C00; font-weight: bold;"&gt;*&lt;/span&gt;&lt;span style="color: #000;"&gt;args&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;,&lt;/span&gt; &lt;span style="color: #CE5C00; font-weight: bold;"&gt;**&lt;/span&gt;&lt;span style="color: #000;"&gt;kwargs&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;):&lt;/span&gt;
            &lt;span style="color: #204A87; font-weight: bold;"&gt;with&lt;/span&gt; &lt;span style="color: #000;"&gt;timeit&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #000;"&gt;output_file&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;):&lt;/span&gt;
                &lt;span style="color: #204A87; font-weight: bold;"&gt;return&lt;/span&gt; &lt;span style="color: #000;"&gt;fn&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #CE5C00; font-weight: bold;"&gt;*&lt;/span&gt;&lt;span style="color: #000;"&gt;args&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;,&lt;/span&gt; &lt;span style="color: #CE5C00; font-weight: bold;"&gt;**&lt;/span&gt;&lt;span style="color: #000;"&gt;kwargs&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;)&lt;/span&gt;

        &lt;span style="color: #204A87; font-weight: bold;"&gt;return&lt;/span&gt; &lt;span style="color: #000;"&gt;wrapper&lt;/span&gt;

    &lt;span style="color: #204A87; font-weight: bold;"&gt;return&lt;/span&gt; &lt;span style="color: #000;"&gt;decorator&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="usage"&gt;Usage&lt;/h2&gt;
&lt;p&gt;You can use a context manager or the decorator both for profiling and timing&lt;/p&gt;</description><author>Posts on rand[om]</author><pubDate>Fri, 08 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ricardoanderegg.com/posts/python-profiling-timing-utils/</guid></item><item><title>Digital clean-up</title><link>https://holzer.online/weeknotes/2023/36/index.html</link><description>&lt;p&gt;
In Gregor Hohpe's book &lt;i&gt;The Software Architect Elevator&lt;/i&gt; there is a chapter with the beautiful headline: If you never kill anything, you will live among zombies - And they will eat your brain. As I hinted &lt;a href="/weeknotes/2023/35/"&gt;last week&lt;/a&gt;, dead and undead code isn't very satisfying to work with, so in the spirit of Ken Thompsons bonmot &lt;q&gt;One of my most productive days was throwing away 1000 lines of code&lt;/q&gt;, my efforts culminated this week in a merge requests which implements a feature in negative 780 SLOC.
&lt;/p&gt;

&lt;p&gt;
And as I was in digital clean-up mood, I also pruned my wiki. Consolidating notes and throwing away drafts that didn't lead anywhere left it both over a hundred entries smaller and more useful to me than before. Sometimes less is more. 
&lt;/p&gt;

&lt;time datetime="2023-09-08" title="That's 2023-09-08 for Non-Discordians"&gt;
Sweetmorn, the 32nd day of Bureaucracy in the YOLD 3189
&lt;/time&gt;</description><author>Fabian's public notepad</author><pubDate>Fri, 08 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://holzer.online/weeknotes/2023/36/index.html</guid></item><item><title>Is dismissing the algorithmic tech interview a memetic hazard?</title><link>https://sebinsua.com/algorithmic-bathwater</link><description>&lt;h3&gt;Algorithmic bathwater&lt;/h3&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;you might not like it, but mindlessly grinding leetcode actually does make you a good programmer&lt;/p&gt;&amp;mdash; kache (@yacineMTB) &lt;a href="https://twitter.com/yacineMTB/status/1687126523910799360?ref_src=twsrc%5Etfw"&gt;August 3, 2023&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;The belief that algorithmic tech interviews do not test for real-world programming skills is a memetic hazard. There might be truth to it but it serves as displacement from what is often the real underlying reason for resistance: the fear of negative judgement.&lt;/p&gt;
&lt;p&gt;Without passing judgement on whether these tests are a good way to assess skill level, it’s crucial to realise that dismissing them has drawbacks. These problems often contain different ways of thinking and require novel techniques or hard-won algorithms, and their deliberate practice can be a valuable way to improve your problem-solving skills. While uncomfortable, acknowledging this offers us an opportunity to grow.&lt;/p&gt;
&lt;h3&gt;Not going in blind&lt;/h3&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;every leetcode problem is just some combination of ~10 fundamental problems. the eigenproblems if you will&lt;/p&gt;&amp;mdash; varepsilon (@var_epsilon) &lt;a href="https://twitter.com/var_epsilon/status/1690755091052077056?ref_src=twsrc%5Etfw"&gt;August 13, 2023&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;Mastering a few fundamental problems can make you more effective at a wide range of challenges, but how do you go about truly understanding these &lt;em&gt;‘eigenproblems’&lt;/em&gt;?&lt;/p&gt;
&lt;p&gt;It’s commonly heard that if you ever need an algorithm you’ll “just Google it”, however this belief is quite suspect as you’re unlikely to reach for Google without awareness of the techniques that might be applicable. Having a foundational understanding can help you to recognise opportunities for application in the first place.&lt;/p&gt;
&lt;p&gt;The creator of the &lt;a href="https://www.teamblind.com/post/New-Year-Gift---Curated-List-of-Top-75-LeetCode-Questions-to-Save-Your-Time-OaM1orEU"&gt;“Blind 75” problem set&lt;/a&gt;, an engineer from Meta, attempted to distill the most useful questions for learning the core concepts and techniques for each category/type of problems from their experience doing 400-500 LeetCode questions during their last job hunt.&lt;/p&gt;
&lt;p&gt;75 questions is still a very large time commitment, and the reality is that it’s even more time-consuming than it might look as fluency generally requires &lt;a href="https://old.reddit.com/r/leetcode/comments/15tcmwq/got_to_100_hards/jwk4e9r/"&gt;spaced repetition of the problems&lt;/a&gt; by doing a variety of topics each week and revisiting these again in subsequent weeks.&lt;/p&gt;
&lt;p&gt;We want the ability to be able to identify and apply the right techniques given an unknown problem, and this is about grasping the intent, design, and the decision-making involved in this. This ability can certainly be picked up incidentally by doing lots of problems and eventually beginning to pattern-match them, but I’d argue that this isn’t the most efficient way to learn as a lot of the knowledge doesn’t appear to be naturally tacit.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://neetcode.io"&gt;Current&lt;/a&gt; &lt;a href="https://www.techinterviewhandbook.org/grind75"&gt;methods&lt;/a&gt; &lt;a href="https://cp-algorithms.com/index.html"&gt;of teaching&lt;/a&gt; often focus on mere implementation, overlooking the decision-making skills needed to identify the right techniques for a problem. This article aims to fill that gap, by articulating what is often left unsaid. While it won’t replace hands-on experience in the nuances of implementation, it should be helpful in improving your understanding of technique &lt;em&gt;‘applicability’&lt;/em&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;h3&gt;Note&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;We’re intentionally not discussing the implementation details of these techniques, as this is already well-covered elsewhere. For deep explanations, video tutorials and code examples, we recommend:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://neetcode.io/practice"&gt;NeetCode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.techinterviewhandbook.org/grind75"&gt;Grind 75&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leetcode.com"&gt;LeetCode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cp-algorithms.com"&gt;CP-Algorithms&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www3.cs.stonybrook.edu/~skiena/373/videos/"&gt;Analysis of Algorithms Lectures&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.algorist.com/algorist.html"&gt;The Stony Brook Algorithm Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.redblobgames.com"&gt;Red Blob Games&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Searching Google&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This article is a work in progress and I’ll be adding more to it over time. &lt;a href="mailto:me@sebinsua.com"&gt;Please email me with any insights or suggestions you have for improvement&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h3 id="what-kind-of-problem-do-i-have"&gt;What kind of problem do I have?&lt;/h3&gt;
&lt;p align="center" width="100%"&gt;
  &lt;a class="without-link-icon" href="./assets/posts/algorithmic-bathwater/algorithmic-bathwater-1.svg"&gt;
   &lt;img alt="Flowchart" src="./assets/posts/algorithmic-bathwater/algorithmic-bathwater-1.svg" /&gt;
  &lt;/a&gt;
&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Is the problem related to a linear/sequential data structure (e.g. array, linked list, string, search space)?&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Are we being asked to find pairs, triplets or sub-arrays that match a constraint?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;To solve this optimally (e.g. &lt;span class="katex"&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;O&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;), we need to be able to easily compare elements in the sequence without using nested loops. We have at most two options:
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Ensuring the sequence is sorted&lt;/em&gt; and then applying the &lt;strong&gt;Two Pointers&lt;/strong&gt; technique:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;We can avoid the need for a nested loop if the sequence is sorted, as we can operate upon the sequence using two explicit pointers, one at the start and one at the end. The pointers clarify which elements are being considered for the condition being evaluated, and &lt;a href="https://archive.ph/y73um"&gt;on each iteration we choose how to move these pointers based on the result of a comparison&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This is particularly useful when a deterministic order matters or when we are looking for unique pairs/triplets.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Using a &lt;strong&gt;Hash Map&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This allows us to store seen elements and to then look up the complement of the current element in the hash table. It’d be a more appropriate choice when the sequence is unsorted and sorting it would break problem constraints, or when we need to remember past elements for comparison but don’t care about their order.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Are we being asked to find a longest/shortest substring or subsequence that matches a particular constraint or to compute aggregate statistics for a particular length subsequence?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;To solve this optimally, we want to be able to re-use the computations from previous windows so that we can update our answer for each window in constant time (e.g. &lt;span class="katex"&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;O&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;) and the whole sequence can be completed in linear time (e.g. &lt;span class="katex"&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;O&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We can do this by applying the &lt;strong&gt;Sliding Window&lt;/strong&gt; technique, either using a fixed window size or a dynamic window size that can expand, shrink or reset based on certain conditions. Please note that these are not implemented in the same way as two pointers problems as explicit pointers towards the start and end of the window, but instead they generally use a single loop iterator and some additional variables to keep track of the window’s characteristics. This is a subtle but important distinction from the two-pointer technique.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;See also: the Rabin-Karp string search algorithm which employs a specialised form of the sliding window technique that utilises rolling hashes to achieve efficient substring matching.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Are we being asked to find a target value or minimum/maximum value in this sequence?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If the sequence is unsorted, there is no way to do better than a linear scan of the sequence (e.g. &lt;span class="katex"&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;O&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;But, if the sequence is sorted we can apply the &lt;strong&gt;Binary Search&lt;/strong&gt; technique.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In fact, we can apply the &lt;strong&gt;Binary Search&lt;/strong&gt; technique if the sequence is &lt;em&gt;partially sorted&lt;/em&gt; as long as it is &lt;em&gt;partitionable&lt;/em&gt; (e.g. we can discern a sorted and unsorted partition on each iteration). For example, we can apply binary search to rotated sorted arrays or bitonic sequences.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We can also apply the &lt;strong&gt;Binary Search&lt;/strong&gt; technique to compute numerical approximations if a sequence is ordered but virtual (e.g. a search space). For example, to approximate the square root of a number.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Are we being asked to detect cycles in a linked list or to find the middle element?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Use the &lt;strong&gt;Fast-And-Slow Pointers&lt;/strong&gt; technique. If the pointers meet, there is a cycle.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If we need to find the middle element, we can rely on the fact that the slow pointer will be at the middle element when the fast pointer reaches the end of the list.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Are we being asked to find duplicates in a sequence?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If the sequence is sorted, then duplicate items will be next to each other and we can find them by comparing adjacent elements in the sequence.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If the sequence is unsorted, we can add elements that we see into a &lt;strong&gt;Set&lt;/strong&gt; or &lt;strong&gt;Hash Map&lt;/strong&gt; and then check whether the current element is within this.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Does the problem require frequent and ordered access to recently processed elements?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;In situations in which you need access to elements in some dynamic order you cannot just use a &lt;code&gt;for&lt;/code&gt;-loop to iterate through some elements in a fixed order, and likely will need to append elements into a dynamically ordered sequence maintained within a &lt;strong&gt;Stack&lt;/strong&gt; or &lt;strong&gt;Queue&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If we need to access the most recently processed elements we can use a &lt;strong&gt;Stack&lt;/strong&gt;. This has &lt;span class="katex"&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;O&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; access at the end that elements are appended.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Note that ‘stack’ is a bit of an overloaded term as it refers to any last-in first-out (LIFO) structures. They are often arrays (or linked lists) but this isn’t necessarily the case. For example, when executing programs we keep a stack of frames within a block of contiguous memory.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Not all problems involving a ‘stack’ are similar. For example, there are some in which (1) you only ever need to access, check or pop the most recently appended element, while there are others in which (2) you will need to repeatedly traverse backwards through the stack while some condition is true. The latter approach would often use a nested &lt;code&gt;while&lt;/code&gt;-loop and can allow you to gather information or maintain state using previous elements in the stack or even to maintain some invariant in the stack that can be depended upon by future iterations or even a second phase of the algorithm.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I think it can also be helpful to think of how other data structures can also have stack-like access patterns. For example, when given a decimal integer we can ‘peek’ at its last digit using &lt;code&gt;% 10&lt;/code&gt; and then ‘pop’ this using a &lt;code&gt;// 10&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If we need to access the least recently processed elements we can use a &lt;strong&gt;Queue&lt;/strong&gt;. This has &lt;span class="katex"&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;O&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; access at its start, at the opposite end to where elements are appended.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Are we being asked to merge sorted lists?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use a &lt;strong&gt;Min-Heap&lt;/strong&gt;. This approach is known as a k-way merge and is a generalisation of the two-way merge that is used in merge sort.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Does the problem involve categorising or identifying unique elements based on some aspect of them not immediately known?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;These are in fact conceptually &lt;strong&gt;Hash Map&lt;/strong&gt; problems (because you cannot use &lt;strong&gt;Set&lt;/strong&gt;s for identifying “uniqueness” when it is not defined by the value of the element itself). However, the crux of these questions often lies not in the choice of data structure, but the creation of a hash-like bucketing function that can efficiently capture/compute some invariant of the element that it can be grouped by. For example, anagrams can be grouped by the frequency of their characters, snowflakes by their lexicographically smallest rotation, and so on.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Does the problem require you to perform efficient lookups of data?&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Do we need to quickly store or lookup strings or provide autocomplete functionality?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use a &lt;strong&gt;Trie&lt;/strong&gt; data structure (also known as a prefix tree). This is a tree data structure that stores strings in a way that allows for fast (e.g. &lt;span class="katex"&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;O&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;k&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;) retrieval and updates of strings that share a common prefix.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Are we being asked to query the smallest/largest/median values? Do you need to be able to read out a sorted sequence of elements?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Use a &lt;strong&gt;Min-Heap&lt;/strong&gt; or &lt;strong&gt;Max-Heap&lt;/strong&gt; data structure to query for the smallest/largest values (in &lt;span class="katex"&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;O&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can use a combination of both &lt;strong&gt;Min-Heap&lt;/strong&gt; and &lt;strong&gt;Max-Heap&lt;/strong&gt; if you want to compute the median.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There are time/space costs to maintaining heaps (creating a heap costs &lt;span class="katex"&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;O&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; but updates are &lt;span class="katex"&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;O&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mop"&gt;lo&lt;span&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; due to the cost to &lt;code&gt;heapify&lt;/code&gt;), so this makes more sense when you expect to make multiple queries over the lifetime of your program. The concept of &lt;a href="https://en.wikipedia.org/wiki/Amortized_analysis"&gt;amortised cost&lt;/a&gt; is also important when comparing min-heaps to algorithms like quickselect. While min-heaps offer a consistent update time of &lt;span class="katex"&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;O&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mop"&gt;lo&lt;span&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;, quickselect’s time complexity may be more efficient for one-off or occasional operations.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Heaps do not maintain sorted order of their elements, and instead only maintain a partial ordering. Because of this, they do not offer &lt;span class="katex"&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;O&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; access to the &lt;span class="katex"&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut" style="height: 0.7102em;"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;k&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height: 0.7102em;"&gt;&lt;span&gt;&lt;span class="pstrut" style="height: 2.5em;"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord text mtight"&gt;&lt;span class="mord sizing reset-size3 size1"&gt;th&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; smallest/largest element or allow you to read out a sorted sequence of elements. If you need to be able to do this, while still allowing for efficient updates, you can use a &lt;strong&gt;Binary Search Tree (BST)&lt;/strong&gt; data structure, possibly augmented with additional information to make it self-balancing (e.g. &lt;strong&gt;AVL Tree&lt;/strong&gt; or &lt;strong&gt;Red-Black Tree&lt;/strong&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Does the problem require you to recover the order of some elements or substrings within a sequence?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If you are able to infer partial information about how particular elements are connected, for example, if they come before or after each other or if they overlap (or touch), you can use this information to help build a &lt;strong&gt;Directed Graph&lt;/strong&gt;. Vertices can represent elements, while edges can represent the information inferred from the data. Once this is achieved, it’s often possible to recover the order by running something like a &lt;strong&gt;Topological Sort&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In general, graphs seem a good fit for problems involving recovering the order of a sequence, for example, “de Bruijn” graphs are used for the re-assembly of de-novo DNA sequences from k-mers in computational biology.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Are you dealing with data points that are interconnected in some way, needing to generate and test paths or sequences, or wishing to traverse elements in a matrix?&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;h3&gt;Note&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Problems like this can often be solved using graph or tree data structures, but it’s important to note that these are not the only ways of representing data that is interconnected. For example, rotating a tree 45-degrees gives you a grid, and you can use an adjacency matrix to represent a graph, or an array to represent a tree (as is done when implementing heaps). The choice of data structure is not only a trade-off between memory usage and performance, but can also lead to different ways of thinking about a problem.&lt;/li&gt;
&lt;li&gt;There are also similarities between iteration of sequences and traversals in graphs, trees or state-spaces. Traversal is essentially an extended form of iteration, designed to handle dynamic, non-linear, branching data structures. Different traversal strategies, such as BFS (level-order) and DFS (pre-order, in-order, post-order), offer specific advantages tailored to the nature of the problem at hand.&lt;/li&gt;
&lt;li&gt;An important insight is the fluidity and overlapping between techniques like trees, dynamic programming and path-finding. This allows for multiple perspectives on a problem and can occasionally offer more elegant or optimal solutions. For example, in Tristan Hume’s &lt;a href="https://thume.ca/2017/06/17/tree-diffing/"&gt;“Designing a Tree Diff Algorithm Using Dynamic Programming and A*”&lt;/a&gt;, the problem of diffing begins as a decision tree before being transformed into a 2D grid that is amenable to dynamic programming style techniques and eventually the path-finding algorithm A*.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Have you been asked to find a combination or permutation of elements that match a constraint?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;When you have a particular target or constraint that needs to be met, it generally suggests that you will need to go deeply into the search space to find and test each path. This suggests that you will need to use a &lt;strong&gt;Depth-First Search (DFS)&lt;/strong&gt; approach, however, this will likely result in an explosion of paths that need to be explored and very slow performance as well as bad memory usage. To avoid this, you will need to prune paths early-on that cannot possibly lead to a solution, and, also due to the potential depth of the search space use a &lt;strong&gt;Backtracking&lt;/strong&gt; approach to (a) invalidate any paths later shown to be impossible, and (b) rollback any modifications to shared state that are made during the search.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Another way of looking at things is that DFS doesn’t need to traverse a materialised tree or graph. They can be used to explore a more implicit solution space such as a “decision tree” in which each choice appends an item to a stack. For example, it would be possible to use something like a regular expression (e.g. &lt;code&gt;(ab[cd]e?)*&lt;/code&gt;) as a generative structure that creates a DFS-like search space that generates strings that match that expression.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Depth-First Search (DFS)&lt;/strong&gt; has a lot of overlap with &lt;strong&gt;Top-Down Memoisation&lt;/strong&gt;. In fact, top-down memoisation is depth-first search but with a caching optimisation to avoid re-computing subproblems (e.g. “overlapping subproblems”). Top-down memoisation is generally assumed to be implemented using recursion, but, it can also be implemented iteratively using a &lt;strong&gt;Stack&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The choice over whether to implement &lt;strong&gt;Depth-First Search (DFS)&lt;/strong&gt; using recursion or with iteration and a stack isn’t merely an aesthetic choice but also has practical implications:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Firstly, recursion presents you with limits due to the maximum recursion depth of your chosen programming language.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;But more importantly, recursion provides a natural mechanism for backtracking as you can simply return from a recursive call to ‘undo’ the last step. On the other hand, iteration with an explicit stack requires you to manually implement backtracking by either copying data structures into each stack item (so that dropping stack items is effectively backtracking) or by using shared state and carefully rolling back changes to this.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;More subjectively, some people prefer recursion for its more intuitive mathematical representation of a problem, while others prefer iteration for its more explicit and easier to debug/trace states.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Do you need to fill or quantify contiguous regions in a matrix?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;You can do so using a &lt;strong&gt;Flood-Fill&lt;/strong&gt; algorithm, which can be either a &lt;strong&gt;Breadth-First Search (BFS)&lt;/strong&gt; or &lt;strong&gt;Depth-First Search (DFS)&lt;/strong&gt; traversal from a starting point with the constraint that you can only move to adjacent cells that are the same ‘color’ as the starting point.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can avoid accidentally revisiting cells by placing them into a &lt;strong&gt;Set&lt;/strong&gt; or &lt;strong&gt;Hash Map&lt;/strong&gt; as you visit them, however, in order to save memory you can also mutate the matrix in-place by marking visited cells with a different ‘color’ (and then, if you wish to, reverting these changes once you have finished).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Is searching for the shortest path between some nodes or doing a ‘level-order’ traversal your main concern?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Use &lt;strong&gt;Breadth-First Search (BFS)&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Note that, BFS uses more memory than DFS because it needs to keep all of the nodes at the current level in memory, while DFS only needs to store the nodes along a single path in the tree/graph as it traverses. Therefore, if memory usage is a concern, DFS could be the better choice.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;BFS traversals use a FIFO queue to traverse nodes and expect the edges to have uniform weights/costs. For non-uniform weight/cost graphs, we swap the FIFO queue for a priority queue (e.g. min-heap) to enable cost-aware traversals using &lt;strong&gt;Dijkstra&lt;/strong&gt;’s or &lt;strong&gt;A*&lt;/strong&gt;. Dijkstra’s prioritises nodes based on cumulative cost from the start finding shortest paths to all nodes, while A* also adds an (&lt;a href="https://en.wikipedia.org/wiki/Admissible_heuristic"&gt;admissible&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Consistent_heuristic"&gt;consistent&lt;/a&gt;) heuristic estimate towards the goal making it goal-oriented and often &lt;a href="https://www.redblobgames.com/pathfinding/a-star/introduction.html"&gt;more efficient for finding a specific target&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Are you being asked to determine whether nodes are connected, to count the number of components in a graph, or to detect cycles?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;In an undirected graph? Use &lt;strong&gt;Union-Find&lt;/strong&gt;. Note that Union-Find is not suitable for directed graphs because it inherently deals with undirected equivalence relations; it merges sets and loses the sense of direction between nodes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In a directed or undirected graph? Use &lt;strong&gt;Depth-First Search (DFS)&lt;/strong&gt;. Unlike Union-Find, DFS is suitable for directed graphs and can also provide information about the shape or structure of components.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Being able to detect whether two nodes are connected can be a useful general optimisation technique when path-finding (e.g. BFS, DFS, Dijkstra or A*) as it can allow you to “early exit” before beginning an invalid traversal.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Does a solution to the problem result in a very expensive branching program suggesting that we might have an optimisation problem?&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;If local optimum decisions appear to lead to a global optimum:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Apply a &lt;strong&gt;Greedy Algorithm&lt;/strong&gt;. Note that there’s no guaranteed way of knowing upfront whether local optimum decisions will lead to a global optimum, and it will require testing and analysis to determine whether this is the case.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If the solution to the problem seems to require future information to decide the current step, a greedy algorithm will not be appropriate and you will need to either use &lt;strong&gt;Depth-First Search (DFS)&lt;/strong&gt; or switch to a &lt;strong&gt;Dynamic Programming&lt;/strong&gt; approach involving &lt;strong&gt;Top-Down Memoisation&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A greedy algorithm makes decisions at each step based solely on the current state and local considerations, and cannot require backtracking, reconsideration of previous decisions, or deeper exploration of decision paths (like depth-first search).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If it seems to be possible to compute a solution from a combination of previously computed solutions (e.g. “optimal substructure”) then we can solve it using &lt;strong&gt;Dynamic Programming&lt;/strong&gt;. Dynamic programming is particularly beneficial when the same subproblem reappears multiple times in a computation (e.g. “overlapping subproblems”).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Dynamic programming allows you to save time at the expense of space.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There are two high-level ways of implementing dynamic programming and the choice depends on the problem:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;We can always apply &lt;strong&gt;Top-Down Memoisation&lt;/strong&gt;. This is effectively a DFS of the state space, generally implemented using recursive memoised function calls. It’s not always the most efficient way to solve the problem due to the overhead of recursion, but it does avoid the need to compute all possible subproblems first.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If the transitions between subproblems in the state space are computed in a fixed manner we can apply &lt;strong&gt;Bottom-Up Tabulation&lt;/strong&gt;. This computes all possible subproblems iteratively first and then uses these to compute the final solution, but it avoids the overhead of recursion by computing the subproblems one-by-one iteratively and is able to store these in an array for lower memory usage.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;🧪🤔📝&lt;/h3&gt;
&lt;p&gt;The following questions present scenarios that test your ability to select the most appropriate technique.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Greedy Algorithm or Dynamic Programming?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://leetcode.com/problems/coin-change/"&gt;Coin Change&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leetcode.com/problems/jump-game/"&gt;Jump Game&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Two Pointers or Sliding Window?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://leetcode.com/problems/container-with-most-water/"&gt;Container With Most Water&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leetcode.com/problems/minimum-window-substring/"&gt;Minimum Window Substring&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Stack or Two Pointers?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://leetcode.com/problems/trapping-rain-water/"&gt;Trapping Rain Water&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leetcode.com/problems/largest-rectangle-in-histogram/"&gt;Largest Rectangle in Histogram&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Stack or Queue?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://leetcode.com/problems/rotting-oranges/"&gt;Rotting Oranges&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leetcode.com/problems/valid-parentheses/"&gt;Valid Parentheses&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Backtracking or DFS?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://leetcode.com/problems/number-of-islands/"&gt;Number of Islands&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leetcode.com/problems/combination-sum/"&gt;Combination Sum&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Union-Find or DFS?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://leetcode.com/problems/course-schedule/"&gt;Course Schedule&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leetcode.com/problems/redundant-connection/"&gt;Redundant Connection&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;DFS or Dynamic Programming?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://leetcode.com/problems/pacific-atlantic-water-flow/"&gt;Pacific Atlantic Water Flow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leetcode.com/problems/minimum-path-sum/"&gt;Minimum Path Sum&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>@sebinsua</author><pubDate>Fri, 08 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://sebinsua.com/algorithmic-bathwater</guid></item><item><title>How to make a partially defective speaker stay powered on (or: The Five Stages of Annoyance)</title><link>https://datalars.com/2023/09/07/how-to-make-a-partially-defective-speaker-stay-powered-on-or-the-five-stages-of-annoyance/</link><description>&lt;h2 id="post-15-acquisition"&gt;Acquisition&lt;/h2&gt;
&lt;p&gt;I bought a new TV a year and a half ago. Much to my dismay, I discovered that while the picture quality is great, the audio quality is not, unlike my previous TV. My daily driver Linux PC is connected to my TV, and is my main entertainment hub for TV shows, movies and video games, and also general PC usage, so I figured I had to get an external speaker or sound bar. My dad had a spare speaker which he had replaced with a newer unit, so I inherited the spare - a &amp;quot;&lt;em&gt;Geneva Sound System Model S DAB&lt;/em&gt;&amp;quot;.&lt;/p&gt;
&lt;p&gt;It came with a small caveat: the speaker will not turn &lt;em&gt;on&lt;/em&gt; using the remote control. Mind you, it will turn &lt;em&gt;off&lt;/em&gt; just fine with the same exact button, and every other button on the remote works as advertised - it just will not turn on using the remote. I figured that this was no big deal, and accepted the speaker with thanks. I get the speaker situated and hooked up, and the audio quality is great - a big upgrade from the built-in TV audio. Great for music, great for movies, great for games, just all-round great...&lt;/p&gt;
&lt;h2 id="post-15-perturbation"&gt;Perturbation&lt;/h2&gt;
&lt;p&gt;...until I read some news, or do anything that doesn't produce any sound for a while. This is when the European Commission decides to take a retroactive shit in my cereal. Since 2009, audiovisual equipment such as speakers are &lt;a href="https://commission.europa.eu/energy-climate-change-environment/standards-tools-and-labels/products-labelling-rules-and-requirements/energy-label-and-ecodesign/energy-efficient-products/mode-standby-and-networked-standby-devices_en"&gt;&lt;strong&gt;required&lt;/strong&gt; to switch to a low-power mode after a reasonable amount of time&lt;/a&gt;. In and of itself, that is a good requirement, and will save power when such devices are not in use. Sometimes it's a bad requirement, and that is when:&lt;/p&gt;
&lt;p&gt;     a) the automatic standby feature can't be disabled, and&lt;br /&gt;
     b) the unit can't be powered on with the remote control&lt;/p&gt;
&lt;h2 id="post-15-bargaining"&gt;Bargaining&lt;/h2&gt;
&lt;p&gt;This particular set of circumstances means that whenever I'm using the computer, and switch between tasks that produce sound, and those that don't, I risk the speaker powering down, and having to get up, walk over to the speaker underneath the TV, and power it back up manually. That wouldn't be so bad if it was a once-a-day thing, but the auto-standby timeout for the speaker is fixed at 30 minutes, so you might see how this gets real old, real fast. The speaker itself works fine, and it's a shame to generate e-waste from something that's only partially defective, so I decide to stick with it. I wouldn't blame someone for chucking it and replacing it with a new unit though, but I am particularly stubborn.&lt;/p&gt;
&lt;p&gt;Upon discovering this, the first thing I do is start searching the web for &lt;a href="http://ch.genevalab.com/wp-content/uploads/2016/08/GenevaSound-S-Manual_Multilingual.pdf"&gt;the manual for the device&lt;/a&gt;, to see if I can address point a) above: turning off the auto-standby function, but I come up short. There doesn't seem to be a way to disable this functionality at all. Since I wouldn't know the first thing about fixing the hardware side of this (fixing the remote not being able to turn the unit on any more), I default to trying to work around it through software.&lt;/p&gt;
&lt;p&gt;My first attempt to trick the speaker into staying awake is playing a sound on an interval. A sound which is in frequency range of the speaker, but out of range of my ears. According to specs &lt;a href="https://radiospesialisten.no/produkt/geneva-model-s-dab-bt-matt-sort/"&gt;found online&lt;/a&gt;, the frequency range for the unit is 75 Hz to 20 kHz, so I try both ends of the spectrum. Using Audacity, I generate 5 second tones ranging from 76 Hz to 19 kHz, and make a script that plays the tone every 10 minutes, and slowly over the course of a few days, work my way through all the generated tones to see if any will keep the speaker awake.&lt;/p&gt;
&lt;p&gt;It doesn't work. None of the tones do. They all play (confirmed by dropping in an audible .wav in place of the generated tones), but it seems as though the speaker is ignoring them. The 17 kHz tone had the added bonus of viscerally startling my friend who could hear the sound (I couldn't), asking what the hell that was.&lt;/p&gt;
&lt;p&gt;Most likely the internal logic that checks for sound, checks for loudness/dB values, or some other obscure black magic fuckery I don't understand (spoilers: I am not an audio technician, and possess only cursory knowledge of audio specifics).&lt;/p&gt;
&lt;p&gt;My second attempt is a much less sophisticated one: play a short, normal, audible sound every 20 minutes or so. I picked out a ~1 second audio file from my OS, and made it play every 20 minutes. This also didn't work. My conclusion is now that the aforementioned internal logic not only checks for loudness/amplitude, but also has a polling rate greater than a second (or at least, greater than the length of the audio file in question). I don't want to play a longer file either since it's on a fixed interval, it will play over any other media I am consuming, and become another point of annoyance.&lt;/p&gt;
&lt;h2 id="post-15-acceptance"&gt;Acceptance&lt;/h2&gt;
&lt;p&gt;At this point, I more or less resign to my fate, and continue using the speaker without any software magic to try to keep it alive. I do the whole song and dance of getting up and turning the speaker back on when I need it, several times a day. My record is 7 times in one long day of intensive programming interspersed by YouTube video breaks.&lt;/p&gt;
&lt;h2 id="post-15-redemption"&gt;Redemption&lt;/h2&gt;
&lt;p&gt;A year and a half passes, to the present day. I am working on some other project, when I notice the speaker turning off again, and spontaneously get an idea. What if I continuously monitor my audio output, and only play an audible sound when it's been quiet for a while? I haven't done anything like that in the past, but after a ton of web searching, and trial and error, I come up with a solution. This relies on using PulseAudio (or PipeWire with its backward compatibility with PA).&lt;/p&gt;
&lt;p&gt;That's right, there's finally going to be some code in this article!&lt;/p&gt;
&lt;p&gt;&lt;code&gt;anti-energy-saving.sh&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env bash
sinkToMonitor=&amp;quot;alsa_output.pci-0000_06_00.1.hdmi-stereo&amp;quot; # find with `pactl list short sinks`
soundToPlay=&amp;quot;/usr/share/sounds/freedesktop/stereo/message-new-instant.oga&amp;quot;
tmpOutputSample=&amp;quot;/tmp/output-sample.flac&amp;quot; # saves the sample in ram and overwrites it every loop
timeoutSecs=1500 # timeout in seconds - 1500s = 25m

trap quit SIGINT
quit() {
    printf &amp;quot;\nSIGINT received, cleaning up and exiting\n&amp;quot;
    rm &amp;quot;$tmpOutputSample&amp;quot;
    exit
}

secsSinceLastSound=0
while true; do
    currDefaultSink=$(pacmd list-sinks | grep &amp;quot;\* index&amp;quot; -A1 | awk '/name:/ {print $2}')

    if [[ &amp;quot;$currDefaultSink&amp;quot; = &amp;quot;&amp;lt;$sinkToMonitor&amp;gt;&amp;quot; ]]; then
        timeout 5 parecord --channels=1 --file-format=flac --device &amp;quot;$sinkToMonitor.monitor&amp;quot; &amp;quot;$tmpOutputSample&amp;quot;
        meanNorm=$(sox &amp;quot;$tmpOutputSample&amp;quot; -n stat 2&amp;gt;&amp;amp;1 | awk '/Mean +norm:/ {print $3}')
        if (( $(echo &amp;quot;$meanNorm &amp;gt; 0.000700&amp;quot; | bc -l) )); then
            secsSinceLastSound=0
            echo &amp;quot;Sound threshold reached, timer reset...&amp;quot;
        else
            secsSinceLastSound=$((secsSinceLastSound+5))
            echo &amp;quot;Sound threshold not reached, timer is at $secsSinceLastSound/$timeoutSecs seconds...&amp;quot;
        fi

        if [[ &amp;quot;$secsSinceLastSound&amp;quot; -ge $timeoutSecs ]]; then
            echo &amp;quot;Timeout reached, playing sound and resetting timer...&amp;quot;
            paplay &amp;quot;$soundToPlay&amp;quot;
            paplay &amp;quot;$soundToPlay&amp;quot;
            secsSinceLastSound=0
        fi
    else
        echo &amp;quot;Active sink changed, sleeping...&amp;quot;
        secsSinceLastSound=0
        sleep 10
    fi
done
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To use this on your system, you'll need to change the values of the variables &lt;code&gt;sinkToMonitor&lt;/code&gt;, &lt;code&gt;soundToPlay&lt;/code&gt; and &lt;code&gt;timeoutSecs&lt;/code&gt; to fit your configuration.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sinkToMonitor&lt;/code&gt; needs to hold the name of your default audio sink, which you can find with &lt;code&gt;pactl list short sinks&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;soundToPlay&lt;/code&gt; is a path to the sound you wish to play when the inactivity timer has been reached&lt;/li&gt;
&lt;li&gt;&lt;code&gt;timeoutSecs&lt;/code&gt; is the number of seconds of inactivity to wait before playing the keep-alive-sound&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Make the script executable with &lt;code&gt;chmod +x anti-energy-saving.sh&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You'll also notice that I call &lt;code&gt;paplay&lt;/code&gt; twice to play the same sound twice. If you use a longer audio file, you won't have to do this, I just preferred this short and inoffensive sound to play whenever the speaker is nearing its auto-standby timeout.&lt;/p&gt;
&lt;p&gt;If you don't speak Bash, this is a quick run-down of how the script functions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Checks whether you're currently using your default audio sink, if not, sleeps for 10 seconds. I added this because I sometimes use a Bluetooth headset, and don't want the script running on that - only the HDMI output.&lt;/li&gt;
&lt;li&gt;If you're using your default audio sink, the script will record 5 seconds' worth of audio that comes out of your speaker to RAM (&lt;code&gt;/tmp&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The audio file is run through &lt;code&gt;sox -n stat&lt;/code&gt; to get the average volume of the samples in the clip - if it exceeds a set threshold, the script will reset the inactivity timer. The reason this comparison is &amp;gt; 0.0007 instead of just any value &amp;gt; 0 is because of the aforementioned speaker polling rate; we need to make sure enough sound has been played lately that the speaker won't ignore it.&lt;/li&gt;
&lt;li&gt;If the audio clip didn't exceed the threshold, 5 seconds is added to the inactivity timer.&lt;/li&gt;
&lt;li&gt;If the inactivity timer is greater than or equal to the timeout value, a sound is played twice to keep the speaker alive, and the inactivity timer is reset&lt;/li&gt;
&lt;li&gt;Repeats ad infinitum&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can remove all &lt;code&gt;echo&lt;/code&gt;es if you'd like, they are just a remnant of me debugging this in an interactive terminal. The script is designed to run non-interactively in the background without any user input.&lt;/p&gt;
&lt;h3 id="post-15-conclusion"&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;The script works great for me and my situation, and I haven't had a single incident of speaker snooze since implementation. I've set it to run on start-up, and it uses very little CPU and RAM. It also won't interrupt any other audio I am playing, since there's a little more thought to it beyond a simple &lt;code&gt;sleep 900&lt;/code&gt;.&lt;/p&gt;</description><author>datalars</author><pubDate>Thu, 07 Sep 2023 21:18:21 GMT</pubDate><guid isPermaLink="true">https://datalars.com/2023/09/07/how-to-make-a-partially-defective-speaker-stay-powered-on-or-the-five-stages-of-annoyance/</guid></item><item><title>Swyx's Simple Guide to Singapore</title><link>https://www.swyx.io/sg-guide</link><description>A personal guide to Singapore for foreign friends visiting.</description><author>swyx's site RSS Feed</author><pubDate>Thu, 07 Sep 2023 19:59:56 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/sg-guide</guid></item><item><title>Predictable programming 3 - using XState</title><link>https://blog.martijnarts.com/predictable-programming-3-using-xstate/</link><description>&lt;p&gt;In the last two posts (&lt;a href="https://blog.martijnarts.com/predictable-programming-1-how-typescript-isnt-rust/"&gt;1&lt;/a&gt;, &lt;a href="https://blog.martijnarts.com/predictable-programming-2-making-typescript-more-like-rust/"&gt;2&lt;/a&gt;) in this series, I explained why I want Typescript to be more predictable, and some of the most important guidelines I follow to make that happen. Here, I want to dive into a specific library that has helped me significantly improve the predictability&lt;/p&gt;</description><author>Martijn Arts</author><pubDate>Thu, 07 Sep 2023 13:15:26 GMT</pubDate><guid isPermaLink="true">https://blog.martijnarts.com/predictable-programming-3-using-xstate/</guid></item><item><title>The Data Driven Falacy</title><link>https://justingarrison.com/blog/2023-09-06-the-data-driven-falacy/</link><description>Lies, damned lies, and data driven.</description><author>Justin Garrison's Homepage</author><pubDate>Thu, 07 Sep 2023 07:01:18 GMT</pubDate><guid isPermaLink="true">https://justingarrison.com/blog/2023-09-06-the-data-driven-falacy/</guid></item><item><title>The Internet Manifesto</title><link>https://lagomor.ph/indieweb/20230906/2146-bookmark/</link><description/><author>Home on Lagomorph</author><pubDate>Thu, 07 Sep 2023 06:46:00 GMT</pubDate><guid isPermaLink="true">https://lagomor.ph/indieweb/20230906/2146-bookmark/</guid></item><item><title>The IKEA-powered homelab on a wall</title><link>https://ounapuu.ee/posts/2023/09/07/ikea-powered-homelab/</link><description>This pegboard can fit so much compute power on it!</description><author>./techtipsy</author><pubDate>Thu, 07 Sep 2023 06:00:00 GMT</pubDate><guid isPermaLink="true">https://ounapuu.ee/posts/2023/09/07/ikea-powered-homelab/</guid></item><item><title>Semver violations are common, better tooling is the answer</title><link>https://predr.ag/blog/semver-violations-are-common-better-tooling-is-the-answer/</link><description>&lt;p&gt;&lt;em&gt;This post is coauthored by &lt;a href="https://tonowak.com/" rel="external"&gt;Tomasz Nowak&lt;/a&gt; and Predrag Gruevski. It describes work the two of us did together with Bartosz Smolarczyk, Michał Staniewski, and Mieszko Grodzicki.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://twitter.com/PredragGruevski/status/1587877518018756609/photo/1" rel="external"&gt;Anecdotally&lt;/a&gt;, &lt;code&gt;cargo-semver-checks&lt;/code&gt; is a helpful tool for preventing the semver violations that every so often cause ecosystem-wide pain.
This is why it earned a spot in the CI pipelines of key Rust crates like &lt;a href="https://github.com/tokio-rs/tokio/blob/37bb47c4a2aff8913e536767645772f15650e6cd/.github/workflows/ci.yml#L393-L403" rel="external"&gt;&lt;code&gt;tokio&lt;/code&gt;&lt;/a&gt;, and also why &lt;a href="https://github.com/obi1kenobi/cargo-semver-checks/issues/61" rel="external"&gt;the &lt;code&gt;cargo&lt;/code&gt; team hopes to integrate it into &lt;code&gt;cargo&lt;/code&gt; itself&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While anedotal evidence is nice, we wanted to get concrete data across a large sample of real-world Rust code.&amp;hellip;
&lt;/p&gt;</description><author>Predrag Gruevski's blog and personal site.</author><pubDate>Thu, 07 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://predr.ag/blog/semver-violations-are-common-better-tooling-is-the-answer/</guid></item><item><title>Emerging from dotemacs bankruptcy the hard way: integrating the IDE (feat. Clojure(Script))</title><link>https://www.evalapply.org/posts/emerging-from-dotemacs-bankruptcy-ide-experience/index.html</link><description>The one in which we design a rich Integrated Development Environment (IDE) experience, using Clojure as our muse. Featuring Language Server Protocol (lsp-mode + clojure-lsp), clojure-mode, cider, and more! Buckle up and get a coffee.</description><author>Aditya Athalye's Eval/Apply Blog</author><pubDate>Thu, 07 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.evalapply.org/posts/emerging-from-dotemacs-bankruptcy-ide-experience/index.html</guid></item><item><title>How to build a tooltip in React without using any library</title><link>https://gourav.io/blog/react-tooltip</link><description>How to build tooltip in React without using any library</description><author>Gourav Goyal</author><pubDate>Thu, 07 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://gourav.io/blog/react-tooltip</guid></item><item><title>ZFS, video files and compression</title><link>https://blog.imraniqbal.org/zfs-compression-and-video-files/</link><description>&lt;p&gt;Did this earlier this week and thought I would share.
My server stores video files for TV shows and movies I have watched.
Other types of files exist but it's almost entirely video files.
These take up quite a bit of space.&lt;/p&gt;
&lt;p&gt;The server consists of a single zpool, aptly named "mediafiles".
ZFS supports compression, but the default is to have it off.
Enabling compression is a single command away (&lt;code&gt;man zfs-set&lt;/code&gt; and &lt;code&gt;man zfsprops&lt;/code&gt;).
One thing to note, compression works going forward.
This means that existing files are not compressed, but any new files written to the pool will be.
If you do want to retroactively compress files, you will need to rewrite your files to disk (by moving and unmoving).&lt;/p&gt;
&lt;p&gt;This is what I did for my 12 TB of data.
If you are smarter than, or me from the future you would know this is an astoundingly stupid idea.
This is why:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If you read the man page properly, a file has to be compressible by 7/8 of its original size for ZFS to consider it for compression&lt;/li&gt;
&lt;li&gt;Video files are already compressed, the codec determines the compression.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This means, that after I had put 12 TB of write load on my zpool, my compression remained at 1.00x.&lt;/p&gt;
&lt;pre class="giallo" style="color: #CCCAC2; background-color: #1F2430;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;❯ doas zfs get all | rg compress&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;mediafiles                  compressratio         1.00x                  -&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;mediafiles                  compression           zstd                   local&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yay 🤦.&lt;/p&gt;</description><author>Imran's Blog</author><pubDate>Thu, 07 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.imraniqbal.org/zfs-compression-and-video-files/</guid></item><item><title>Super Boring Topic</title><link>https://backendhance.com/blog/2023/super-boring-topic/</link><description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;p&gt;Gestern habe ich einen Artikel über die Assert Klasse von Spring veröffentlicht. Und im Mai, &lt;a href="https://backendhance.com/slides/pragmatic-spring.pdf"&gt;beim Developer Meetup in Essen&lt;/a&gt;
habe ich auch über die Klasse gesprochen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Aber eigentlich ist sie super langweilig.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Für alle, die sie nicht kennen:&lt;/p&gt;
&lt;p&gt;Sie bietet eine Reihe an simplen &amp;ldquo;assert&amp;rdquo; Methoden. Zum Beispiel statt&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;someVariable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;throw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;IllegalArgumentException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"someVariable must not be null"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;können wir schreiben:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isNotNull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;someVariable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"someVariable must not be null"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Wahnsinn.&lt;/strong&gt;&lt;/p&gt;</description><author>Backendhance</author><pubDate>Thu, 07 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/super-boring-topic/</guid></item><item><title>My overkill Homeserver</title><link>https://gero.dev/blog/my-overkill-homeserver</link><description>My journey to an overkill homeserver with super fast internet speed and powerful hardware.</description><author>Gero Gerke</author><pubDate>Thu, 07 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://gero.dev/blog/my-overkill-homeserver</guid></item><item><title>The Skiatron and Early Dark Trace CRTs</title><link>https://www.masswerk.at/nowgobang/2023/skiatron</link><description>A glimpse of an alternative display technology.</description><author>mass:werk – Now Go Bang!</author><pubDate>Wed, 06 Sep 2023 08:05:00 GMT</pubDate><guid isPermaLink="true">https://www.masswerk.at/nowgobang/2023/skiatron</guid></item><item><title>Improving User Experience with Auto-Focus in 2FA Input</title><link>https://www.danielcorin.com/til/userscripts/focusing-on-an-input/</link><description>Improving User Experience with Auto-Focus in 2FA Input</description><author>Thought Eddies</author><pubDate>Wed, 06 Sep 2023 03:05:00 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/userscripts/focusing-on-an-input/</guid></item><item><title>Seamless settings management with Pydantic and Google Cloud Secret Manager</title><link>https://blog.christoolivier.com/p/seamless-settings-management-with</link><description>Pydantic's settings management capabilities make configuring your application easy across different environments, and you can easily extend it to new sources.</description><author>Christo’s Blog</author><pubDate>Tue, 05 Sep 2023 22:45:19 GMT</pubDate><guid isPermaLink="true">https://blog.christoolivier.com/p/seamless-settings-management-with</guid></item><item><title>Aging Code</title><link>https://vadimkravcenko.com/shorts/aging-code/</link><description>&lt;p&gt;Question:&lt;/p&gt;
&lt;/p&gt;
&lt;p&gt; Answer:&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://vadimkravcenko.com/shorts/aging-code/"&gt;Aging Code&lt;/a&gt; appeared first on &lt;a href="https://vadimkravcenko.com"&gt;Vadim Kravcenko&lt;/a&gt;.&lt;/p&gt;</description><author>Vadim Kravcenko</author><pubDate>Tue, 05 Sep 2023 21:26:44 GMT</pubDate><guid isPermaLink="true">https://vadimkravcenko.com/shorts/aging-code/</guid></item><item><title>Canyoneering The Narrows: How “Dune” Became My Adventure Guide</title><link>https://www.mattp.tech/the-pittsburgh-roboticist-blog/canyoneering-the-narrows-how-dune-became-my-adventure-guide</link><description>Embarking on a canyoneering adventure in Zion National Park's The Narrows, 
I found an unlikely guide: Frank Herbert's 'Dune.' From dressing like a 
Fremen to walking without rhythm, discover how this sci-fi classic became 
my real-world adventure manual. Life imitates art—even in the great 
outdoors.</description><author>The Pittsburgh Roboticist Blog - Matt Puchalski - Pittsburgh Roboticist</author><pubDate>Tue, 05 Sep 2023 20:21:38 GMT</pubDate><guid isPermaLink="true">https://www.mattp.tech/the-pittsburgh-roboticist-blog/canyoneering-the-narrows-how-dune-became-my-adventure-guide</guid></item><item><title>Everyone’s Getting Ghosted</title><link>https://medium.com/@k0ryk/everyones-getting-ghosted-dbf0fbaf161?source=rss-3cf45d4f3676------2</link><description>&lt;h4&gt;The new normal in tech recruiting&lt;/h4&gt;&lt;p&gt;The company I interviewed for was up and coming, YC funded, now profitable, a well-known player in the generative AI space. I was excited for the opportunity. They have many in-house recruiting resources and a policy to &lt;strong&gt;not&lt;/strong&gt; ghost candidates. Yet, I got ghosted. I’m not alone in this, and we’re going to investigate why this happens.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Ghosting&lt;/strong&gt; is a term that refers to asynchronous communication that is abruptly cut off by one party. The term is typically used in dating, but works well for tech recruiting too. In order for it to be ghosting, the ghosted party has to expect the conversation will continue. This means that if you apply and never hear back from a job, that’s not ghosting, a conversation never started. It only becomes ghosting when there is an expected next step that never happens.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jQgQsYv3f0BzqNf97Bx8TQ.png" /&gt;&lt;figcaption&gt;Once ghosted, do we become ghosts? Source: Dall-E&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;The effect of getting ghosted for me, and likely others, is impactful. The narrative in my head played out like this: “was I really so bad that they wouldn’t even tell me no?” It was also a chilling effect for my projects that involved generative AI. I didn’t want to work on them. Every time I logged into my Github, I was reminded of the company because I still had a fork of the take home assignment. It took about a month for my project work to feel normal again, but I shelved some of my LLM projects and moved on. The fire of disappointment had smoldered into an ember of resentment.&lt;/p&gt;&lt;p&gt;The resentment that, months later, inspired me to write a message to the team’s Product Manager (PM). I didn’t reach out on Linkedin. It was much more intimate, using my gamer tag. Discord is a social media platform where you can join communities that have chat rooms, forums, video and voice chat. I am a member of 50+ Discord communities, and many of them are dedicated to open source projects or technologies. The PM was in a Discord I was active in for an open source LLM toolset. Seeing that they were still in the server, and not wanting them to entice others to get ghosted. I private messaged them with the story of my experience. Thankfully, he was receptive.&lt;/p&gt;&lt;p&gt;I had finished a 3-hour take home test and went through hours of Zoom interviews. My loop was done. I didn’t feel great about my performance, but didn’t feel bad. I had done well, but not perfect. My experience was not well aligned with the role. They were looking for a senior level engineer they deemed an “AI hacker.” I had spent the previous decade in technical lead roles, spending as much time on technical direction as I have with coding. I was expecting a message in the next two weeks saying “no,” but would be pleasantly surprised if they said “yes.”&lt;/p&gt;&lt;p&gt;Two weeks after my final interview, I hear nothing. I am tired of waiting for the no, so I send an email to the recruiter asking for more details. I thought that something probably happened in the process and I slipped through the cracks, and I want to give them the opportunity to do the right thing. The right thing being not ghosting me. The recruiter never responded.&lt;/p&gt;&lt;blockquote&gt;Ugh. That’s tough to hear.&lt;/blockquote&gt;&lt;p&gt;The PM says after I tell them about this part in the story.&lt;/p&gt;&lt;blockquote&gt;Let me go talk to our talent team and see what went on here. Really appreciate you sharing this with me.&lt;/blockquote&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/976/1*hhekmTxWDtu5A16KohE8PQ.png" /&gt;&lt;figcaption&gt;Ghost in an email shell. Source: Dall-E&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;There is this sort of liminal space between interpreting behavior as a mistake or malicious, at least for the story I tell in my head. I remind myself of &lt;a href="https://simple.wikipedia.org/wiki/Hanlon%27s_razor"&gt;Hanlon’s razor&lt;/a&gt;, “never attribute to malice that which is adequately explained by [misunderstanding].” When the weeks passed by with no response from the recruiter, the story in my head solidified — it’s malice. However, after the PM’s attempted reconciliation and more context, it seems like it was probably misunderstanding, or in the actual quote from Wikipedia: “stupidity.” A week after the PM got back to me, I get this email:&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/635/0*20it0a7pKcFls5yC.png" /&gt;&lt;figcaption&gt;Mixed feelings of 🙏and🖕. Source: Me&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;I didn’t bomb the interview and feedback was generally positive, though vague. Their policy is to not ghost candidates, and it seems I slipped through the cracks. This works for me as closure. I will not be responding to the recruiting manager. I thanked the PM for helping me out, solid person, really appreciate them.&lt;/p&gt;&lt;h4&gt;Ghost World&lt;/h4&gt;&lt;p&gt;I had not been ghosted by an interviewer before. I typically apply to positions where I know people, so that may be why. I reached out to some coding communities I am in to ask for their experiences. Here are the parts of the discussion that stuck out:&lt;/p&gt;&lt;blockquote&gt;Almost all of the people i know have been ghosted multiple times by potential employers&lt;/blockquote&gt;&lt;blockquote&gt;I had an HR lady tell me ‘we’ll definitely reach out one way or another we’re not one of those places that will leave you hanging’ and then proceed to leave me hanging&lt;/blockquote&gt;&lt;blockquote&gt;recruiters have really been shit the past couple years with actually responding&lt;br /&gt;in the tech world…. Literally every single company i interviewed at (including some really big well known ones) recently have ghosted me&lt;/blockquote&gt;&lt;p&gt;This is a small sample, and does not include all the people who weren’t ghosted. These people are smart and talented with varying levels of experience.&lt;/p&gt;&lt;p&gt;Knowing the skillsets of others who got ghosted and my personal experience, here are some of my assumptions about the recruitment ghosting trend:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;It has nothing to do with how well you interviewed (you could be 2nd pick and still get ghosted).&lt;/li&gt;&lt;li&gt;The majority of people who participated in the interview are not aware of the ghosting. The people who performed the interview probably assume they have humane recruiting practices.&lt;/li&gt;&lt;li&gt;There is no perceived incentive from recruiters to not ghost&lt;/li&gt;&lt;li&gt;Verifying or measuring contact with rejected candidates is not a priority and not done. Even if there is a policy, there is nobody checking to see if it is followed.&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Nonperformative policy&lt;/h4&gt;&lt;blockquote&gt;the message that they intended to send to you never made it to your inbox. You were meant to receive our decision with a link to book time for feedback.&lt;/blockquote&gt;&lt;p&gt;In Sarah Ahmed’s &lt;em&gt;Complaint! &lt;/em&gt;she talks about institutions and their policies, describing many as “nonperformative,” a term which “seemed to capture how saying something was not doing something.” Some policies exist simply to bolster the institutions image, like an anti-bullying policy or inclusion policy.&lt;/p&gt;&lt;p&gt;Nonperformative policies are not necessarily malicious, they come about when there is no investment or measurement into the success of the policy. In my case, the policy to follow up with every candidate was not appropriately measured, therefore allowing me to “slip through the cracks.”&lt;/p&gt;&lt;p&gt;I wonder how many other candidates have slipped through the cracks as well. From the recruiter’s perspective, there is very little incentive to do a final follow up with a rejected candidate. That person is no longer part of the pool of potential candidates, which is the group of people who will make them money. They will definitely prioritize profitable communications over closing the loop with a rejected candidate.&lt;/p&gt;&lt;h4&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;From a game theory perspective if “winning” is having a job that fits all your wants and needs. Then the best way to win is to look and apply for jobs constantly, not just when you are discontent with your current job. That means a lot of opportunities for learning and improving interviewing skills. When recruiters ghost us, it becomes incredibly difficult to gauge our performance in those interviews and continue to improve.&lt;/p&gt;&lt;p&gt;The truth is that getting ghosted by recruiters (or anyone) feels bad. Spending 10 hours on an interview process to not even get the time of day for a “no” is an inhumane practice and should be stopped. It cannot just be stopped with a policy, there needs to be measurement of compliance to the policy. The whole thing could be automated rather simply.&lt;/p&gt;&lt;p&gt;We all know that working with recruiters can be frustrating from both a candidate and interviewer perspective. Those of us who perform interviews regularly in our professional lives should be more aware and verify that loops are closed for candidates. I have so many great conversations with potential coworkers, but a lot of times it doesn’t work out. I want those people to be treated with dignity and respect, and the easiest way I can do that is to ping the recruiter I work with on Slack and verify. So, that’s what I’ll do. Thanks for reading.&lt;/p&gt;&lt;img alt="" height="1" src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=dbf0fbaf161" width="1" /&gt;</description><author>Stories by Kory Kirk on Medium</author><pubDate>Tue, 05 Sep 2023 18:44:51 GMT</pubDate><guid isPermaLink="true">https://medium.com/@k0ryk/everyones-getting-ghosted-dbf0fbaf161?source=rss-3cf45d4f3676------2</guid></item><item><title>What Suboptimal Public Transit and Your Data Pipelines Have in Common</title><link>https://bytesdataaction.substack.com/p/data-pipelines-public-transit</link><description>Whenever I travel to new cities, I always make it a point to take public transit if possible.</description><author>Bytes, Data, Action!</author><pubDate>Tue, 05 Sep 2023 16:48:59 GMT</pubDate><guid isPermaLink="true">https://bytesdataaction.substack.com/p/data-pipelines-public-transit</guid></item><item><title>US Citizenship</title><link>https://ingo-richter.io/post/2023/us-citizenship/</link><description>&lt;h2 id="im-a-us-citizen-now---"&gt;I&amp;rsquo;m a US Citizen Now 🇩🇪 + 🇺🇸&lt;/h2&gt;
&lt;h3 id="tldr"&gt;TL;DR&lt;/h3&gt;
&lt;p&gt;On July 31, 2023, I received my US Citizenship certificate during the Oath Ceremony at the USCIS Field Office in San Francisco. The whole process from filing BBG to certificate took ~22 Months. There was a delay in the biometrics appointment since I was in Germany for a more extended period. I now have German and US Citizenship and can freely roam between countries.&lt;/p&gt;</description><author>Ingo Richter on Today is Tomorrow's Past</author><pubDate>Tue, 05 Sep 2023 10:00:00 GMT</pubDate><guid isPermaLink="true">https://ingo-richter.io/post/2023/us-citizenship/</guid></item><item><title>Breaking a Bit</title><link>https://www.damninteresting.com/breaking-a-bit/</link><description>&lt;p&gt;&lt;img alt="" height="325" src="https://www.damninteresting.com/wp-content/uploads/2023/09/Screenshot-2023-09-04-at-12.25.16-PM-400x325.jpg" style="width: 400px;" title="" width="400" /&gt;It&amp;#8217;s been a busy summer, and the large shortfall in donations last month has been demoralizing, so we&amp;#8217;re taking a week off to rest and recuperate. The curated links section will be (mostly) silent, and behind the scenes we&amp;#8217;ll be taking a brief break from our usual researching, writing, editing, illustrating, narrating, sound designing, coding, et cetera. We plan to return to normalcy on the 11th of September. (The word &amp;#8220;normalcy&amp;#8221; was not considered an acceptable alternative to &amp;#8220;normality&amp;#8221; until 14 May 1920, when then-presidential-candidate Warren G. Harding misused the mathematical term in a campaign speech, stating that America needed, &amp;#8220;not nostrums, but normalcy.&amp;#8221; He then integrated this error into his campaign slogan, &amp;#8220;Return to Normalcy.&amp;#8221; Also, the G in Warren G. Harding stood for &amp;#8220;Gamaliel.&amp;#8221;)&lt;/p&gt;
&lt;p&gt;While we are away, on 06 September 2023, Damn Interesting will be turning 18 years old. To celebrate, here are the first emojis to ever appear in the body of a Damn Interesting post: &amp;#x1f382;&amp;#x1f389;&amp;#x1f381;&lt;/p&gt;
&lt;p&gt;If you become bored while we are away, you might try a little mobile game we&amp;#8217;ve been working on called &lt;a href="http://wordwhile.com" rel="noopener noreferrer" target="_blank"&gt;Wordwhile&lt;/a&gt;. It can be played alone, or with a friend. If you enjoy games like Scrabble and Wordle, you may find this one &lt;i&gt;ENJOYABLE&lt;/i&gt; (75 points). &lt;/p&gt;
&lt;p&gt;&lt;a class="button" href="http://wordwhile.com" rel="noopener noreferrer" target="_blank"&gt;Launch Wordwhile &amp;rarr;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And, as always, &lt;a href="https://www.damninteresting.com/explore/"&gt;there are lots of ways to explore our back-catalog&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.damninteresting.com/breaking-a-bit/"&gt;View this post &amp;#x25b6;&lt;/a&gt;&lt;/p&gt;</description><author>Damn Interesting</author><pubDate>Tue, 05 Sep 2023 04:08:52 GMT</pubDate><guid isPermaLink="true">https://www.damninteresting.com/breaking-a-bit/</guid></item><item><title>Inline previous result and why you should edebug</title><link>https://xenodium.com/inline-previous-result-and-why-you-should-edebug</link><description>&lt;p&gt;Artur Malabarba's &lt;a href="https://endlessparentheses.com/debugging-emacs-lisp-part-1-earn-your-independence.html"&gt;Debugging Elisp Part 1: Earn your independence&lt;/a&gt; is nearly a decade old, yet it rings just as true today.&lt;/p&gt;
&lt;p&gt;Learning to Edebug really &lt;em&gt;&amp;quot;is the right decision for anyone who doesn't know how to Edebug.&amp;quot;&lt;/em&gt; Why, you may ask? He best puts it as &lt;em&gt;&amp;quot;running into errors is not only a consequence of tinkering with your editor, it is the only road to graduating in Emacs.&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For me personally, it &lt;em&gt;earned me that independence&lt;/em&gt; to bend Emacs my way. Don't like how something works? Pull up the debugger to help me understand how a package or function works. I've done this countless of times to bend things my way.&lt;/p&gt;
&lt;p&gt;Speaking of edebug, I had been meaning to tweak edebug's result display behaviour for quite some time. As you step through code, edbug prints the result of previous expressions to the minibuffer. This works well, but I couldn't help but feel like my eyes were constantly jumping between the code and the minibuffer at the bottom of the window.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/inline-previous-result-and-why-you-should-edebug/edebug-minibuffer.gif" /&gt;&lt;/p&gt;
&lt;p&gt;I wanted to minimize the eye jumping experience, so I figured I could likely bend things my way and print the result at point. How did I go about it? The same way I often do. Figure out what function is called for a given key binding via &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Key-Help.html"&gt;describe-key&lt;/a&gt; or my favourite replacement helpful-key from &lt;a href="https://github.com/Wilfred/helpful"&gt;helpful.el&lt;/a&gt;. This led me to &lt;code&gt;edebug-next-mode&lt;/code&gt; in &lt;code&gt;edebug.el&lt;/code&gt;. At that point, I could have set a breakpoint in &lt;code&gt;edebug-next-mode&lt;/code&gt; and eventually step into the relevant code, but hey we had a better clue. We knew that all output started with &amp;quot;Result:&amp;quot;, so we could just search for that string in &lt;code&gt;edebug.el&lt;/code&gt; instead. Jackpot! &lt;code&gt;edebug-compute-previous-result&lt;/code&gt; and its adjacent &lt;code&gt;edebug-previous-result&lt;/code&gt; are just the right functions:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(defun edebug-compute-previous-result (previous-value)
  (if edebug-unwrap-results
      (setq previous-value
            (edebug-unwrap* previous-value)))
  (setq edebug-previous-result
        (concat &amp;quot;Result: &amp;quot;
                (edebug-safe-prin1-to-string previous-value)
                (eval-expression-print-format previous-value))))

(defun edebug-previous-result ()
  &amp;quot;Print the previous result.&amp;quot;
  (interactive)
  (message &amp;quot;%s&amp;quot; edebug-previous-result))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can see that &lt;code&gt;edebug-previous-result&lt;/code&gt; invokes &lt;code&gt;message&lt;/code&gt; which is responsible for displaying the debugged expression's result in the minibuffer. Modifying this functions behaviour would be enough to achieve inline display, but I also want to remove &amp;quot;Result:&amp;quot; from the displayed message. Neither of these functions offer configurability, so we'll resort to advising both functions. That is, &lt;a href="https://en.wikipedia.org/wiki/Monkey_patch"&gt;monkey patch&lt;/a&gt; them (errm I know… lovely).&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(defun adviced:edebug-compute-previous-result (_ &amp;amp;rest r)
  &amp;quot;Adviced `edebug-compute-previous-result'.&amp;quot;
  (let ((previous-value (nth 0 r)))
    (if edebug-unwrap-results
        (setq previous-value
              (edebug-unwrap* previous-value)))
    (setq edebug-previous-result
          (edebug-safe-prin1-to-string previous-value))))

(advice-add #'edebug-compute-previous-result
            :around
            #'adviced:edebug-compute-previous-result)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;adviced:edebug-compute-previous-result&lt;/code&gt; removes &amp;quot;Result:&amp;quot; in addition to dropping &lt;code&gt;(eval-expression-print-format previous-value)&lt;/code&gt;, which I don't typically rely on.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(require 'eros)

(defun adviced:edebug-previous-result (_ &amp;amp;rest r)
  &amp;quot;Adviced `edebug-previous-result'.&amp;quot;
  (eros--make-result-overlay edebug-previous-result
    :where (point)
    :duration eros-eval-result-duration))

(advice-add #'edebug-previous-result
            :around
            #'adviced:edebug-previous-result)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;adviced:edebug-previous-result&lt;/code&gt; is in charge of display via &lt;code&gt;message&lt;/code&gt;, so all we need is some replacement. I initially played with &lt;a href="https://github.com/auto-complete/popup-el"&gt;popup-tip&lt;/a&gt; and that &lt;a href="https://indieweb.social/@xenodium/111008598580447299"&gt;did the job just fine&lt;/a&gt;, but &lt;a href="https://emacs.ch/@fosskers"&gt;Colin&lt;/a&gt; led me to a better path while &lt;a href="https://emacs.ch/@fosskers/111009811997698187"&gt;pointing to Clojure and Common Lisp&lt;/a&gt;. This reminded me of &lt;a href="https://github.com/xiongtx/eros"&gt;eros: Evaluation Result OverlayS for Emacs Lisp&lt;/a&gt;, which I already used. Swapping &lt;code&gt;message&lt;/code&gt; for &lt;code&gt;eros--make-result-overlay&lt;/code&gt; did the trick. Yes, this is a private function, but I can live with that. This code is only an &lt;code&gt;advice-remove&lt;/code&gt; away from disabling, but hey look at those &lt;em&gt;inline results&lt;/em&gt;!&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/inline-previous-result-and-why-you-should-edebug/edebug-inline.gif" /&gt;&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Tue, 05 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/inline-previous-result-and-why-you-should-edebug</guid></item><item><title>Implementing Result Types for C</title><link>https://xnacly.me/posts/2023/result-types-c/</link><description>Implementing a Rust like Result type for and in c</description><author>xnacly - blog</author><pubDate>Tue, 05 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xnacly.me/posts/2023/result-types-c/</guid></item><item><title>Python tip 33: sorting iterables based on multiple conditions</title><link>https://learnbyexample.github.io/tips/python-tip-33/</link><description>&lt;p&gt;In an &lt;a href="https://learnbyexample.github.io/tips/python-tip-21/"&gt;earlier tip&lt;/a&gt;, you learned how to sort iterables based on a key. You can use a sequence like &lt;code&gt;list&lt;/code&gt; or &lt;code&gt;tuple&lt;/code&gt; to specify a tie-breaker condition when two or more items are deemed equal under the primary sorting rule.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;books &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Mage Errant'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Piranesi'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Cradle'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'The Weirkey Chronicles'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Mistborn'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# sorts based on the number of words
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# retains original order for items with the same number of words
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;sorted&lt;/span&gt;&lt;span&gt;(books, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;key&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=lambda &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;b&lt;/span&gt;&lt;span&gt;: b.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;count&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;' '&lt;/span&gt;&lt;span&gt;))
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Piranesi'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Cradle'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Mistborn'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Mage Errant'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'The Weirkey Chronicles'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# items with the same number of words are further sorted in alphabetic order
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;sorted&lt;/span&gt;&lt;span&gt;(books, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;key&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=lambda &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;b&lt;/span&gt;&lt;span&gt;: (b.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;count&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;' '&lt;/span&gt;&lt;span&gt;), b))
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Cradle'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Mistborn'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Piranesi'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Mage Errant'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'The Weirkey Chronicles'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To sort in descending order, usually the &lt;code&gt;reverse=True&lt;/code&gt; keyword argument is used. But what if the primary and secondary rules are opposites? If one of the rule is numerical in nature, you can simply negate the number to reverse the order.&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #7f8989;"&gt;# descending order based on the number of words
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# ascending alphabetic order for items with the same number of words
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; sorted(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;books,&lt;/span&gt;&lt;span&gt; key=lambda b: (-b.count(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;' '&lt;/span&gt;&lt;span&gt;), b))
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'The Weirkey Chronicles'&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Mage Errant'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Cradle'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Mistborn'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Piranesi'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# reverse the above result
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; sorted(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;books,&lt;/span&gt;&lt;span&gt; key=lambda b: (-b.count(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;' '&lt;/span&gt;&lt;span&gt;), b)&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;,&lt;/span&gt;&lt;span&gt; reverse=True)
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Piranesi'&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Mistborn'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Cradle'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Mage Errant'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'The Weirkey Chronicles'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also &lt;a href="https://docs.python.org/3/howto/sorting.html"&gt;docs.python HOWTOs: Sorting&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/100_page_python_intro"&gt;100 Page Python Intro&lt;/a&gt; ebook.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 05 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/python-tip-33/</guid></item><item><title>Brag post: Hacker News Front Page entries</title><link>https://learnbyexample.github.io/mini/hacker-news-front-page-brag/</link><description>&lt;p&gt;In case you haven't yet read this nice post &lt;a href="https://jvns.ca/blog/brag-documents/"&gt;&amp;quot;Get your work recognized: write a brag document&amp;quot;&lt;/a&gt; by Julia Evans, please do that first.&lt;/p&gt;
&lt;p&gt;I definitely found it nice to collect which of my content have reached Hacker News front page over the past 4 years. As I wrote in &lt;a href="https://learnbyexample.github.io/my-book-writing-experience/"&gt;my book writing experience post&lt;/a&gt;, the responses I got for my GNU awk one-liners collection was one of the stepping stones towards my career as a technical author.&lt;/p&gt;
&lt;p&gt;Here's the list so far, ordered by oldest first:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=15549318"&gt;Learn to use Awk with hundreds of examples&lt;/a&gt; — &lt;em&gt;478 points, Oct 2017, 116 comments&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=20212622"&gt;Show HN: I wrote a book on GNU grep and ripgrep&lt;/a&gt; — &lt;em&gt;182 points, June 2019, 53 comments&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=20645319"&gt;Show HN: I wrote a book on Python regular expressions&lt;/a&gt; — &lt;em&gt;193 points, Aug 2019, 50 comments&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=22758217"&gt;Show HN: An eBook with hundreds of GNU Awk one-liners&lt;/a&gt; — &lt;em&gt;539 points, April 2020, 48 comments&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=24637797"&gt;Show HN: Ruby One-Liners Cookbook&lt;/a&gt; — &lt;em&gt;191 points, Sept 2020, 36 comments&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=25006829"&gt;Perl One-Liners Cookbook&lt;/a&gt; — &lt;em&gt;126 points, Nov 2020, 47 comments&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=26076721"&gt;Show HN: &amp;quot;100 Page Python Intro&amp;quot; eBook&lt;/a&gt; — &lt;em&gt;107 points, Feb 2021, 26 comments&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=26356095"&gt;Paying my bills with 'free' ebooks&lt;/a&gt; — &lt;em&gt;85 points, Mar 2021, 22 comments&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=28798095"&gt;Show HN: &amp;quot;Command line text processing with GNU Coreutils&amp;quot; eBook&lt;/a&gt; — &lt;em&gt;117 points, Oct 2021, 20 comments&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=29391107"&gt;Show HN: Improve your Python regex skills with 75 interactive exercises&lt;/a&gt; — &lt;em&gt;175 points, Nov 2021, 12 comments&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=29837543"&gt;Vim prank: alias vim='vim -y'&lt;/a&gt; — &lt;em&gt;341 points, Jan 2022, 259 comments&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=30684232"&gt;Vim Reference Guide&lt;/a&gt; — &lt;em&gt;244 points, Mar 2022, 110 comments&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=33931677"&gt;Show HN: Interactive exercises for Linux CLI text processing commands&lt;/a&gt; — &lt;em&gt;69 points, Dec 2022, 7 comments&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=37290356"&gt;CLI text processing with GNU awk&lt;/a&gt; — &lt;em&gt;419 points, Aug 2023, 129 comments&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As is the case with other social media platforms, being an active participant on Hacker News definitely helps. Apart from commenting on other topics, I also post links to projects and resources that I felt were useful. The number of such links reaching front page outnumbers my own content links.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 05 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/mini/hacker-news-front-page-brag/</guid></item><item><title>Suggested readings week 35/2023</title><link>https://www.stefanocosta.org/suggested-readings-week-35-2023/</link><description>&lt;hr /&gt;&lt;h3 id="superintelligence-cannot-be-contained"&gt;&lt;a href="https://arxiv.org/abs/1607.00913?ref=stefanocosta.org" rel="nofollow noreferrer noopener"&gt;Superintelligence cannot be contained&lt;/a&gt;&lt;/h3&gt;&lt;blockquote&gt;Superintelligence is a hypothetical agent that possesses intelligence far surpassing that of the brightest and most gifted human minds.&lt;/blockquote&gt;&lt;p&gt;The recent advances in machine learning, artificial intelligence and machine comptutation capabilities lead scientists, philosophers and technologists to discuss about the potential catastrophic risks posed by superintelligence.&lt;/p&gt;</description><author>Food for Thought (by Stefano Costa)</author><pubDate>Mon, 04 Sep 2023 21:29:30 GMT</pubDate><guid isPermaLink="true">https://www.stefanocosta.org/suggested-readings-week-35-2023/</guid></item><item><title>Removing the First Line of a File</title><link>https://www.danielcorin.com/til/shell/remove-first-line-of-file/</link><description>Removing the First Line of a File</description><author>Thought Eddies</author><pubDate>Mon, 04 Sep 2023 15:39:00 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/shell/remove-first-line-of-file/</guid></item><item><title>It’s not magic (part 2)</title><link>https://letterstoanewdeveloper.com/2023/09/04/its-not-magic-part-2/</link><description>Dear new developer, When you see a senior engineer who knows the code base like the back of her hand maneuver around it at the speed of thought, it sure looks like magic. When a mysterious bug pops up and you spend a couple of hours trying to replicate it, but then an old hand &amp;#8230; &lt;a class="more-link" href="https://letterstoanewdeveloper.com/2023/09/04/its-not-magic-part-2/"&gt;Continue reading &lt;span class="screen-reader-text"&gt;It&amp;#8217;s not magic (part&amp;#160;2)&lt;/span&gt; &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;</description><author>Letters To A New Developer</author><pubDate>Mon, 04 Sep 2023 15:16:47 GMT</pubDate><guid isPermaLink="true">https://letterstoanewdeveloper.com/2023/09/04/its-not-magic-part-2/</guid></item><item><title>How to Add a Visual Label to Web Content</title><link>https://blog.daniemon.com/2023/09/04/how-to-add-html-visual-label/</link><description>An efficient and maintainable way to add visual labels to your HTML pages, using data attributes and the CSS 'content' property.</description><author>Daniemon's Blog</author><pubDate>Mon, 04 Sep 2023 15:13:25 GMT</pubDate><guid isPermaLink="true">https://blog.daniemon.com/2023/09/04/how-to-add-html-visual-label/</guid></item><item><title>Outch 😵</title><link>https://backendhance.com/blog/2023/outch/</link><description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;h2 id="change-tut-weh"&gt;
&lt;a class="anchor" href="#change-tut-weh"&gt;Change tut weh.&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Der Satz ist etwas abgebrüht.&lt;/p&gt;
&lt;p&gt;Aber es ist mir vor einigen Wochen nochmal bewusst geworden.&lt;/p&gt;
&lt;p&gt;Toni, ein externer DevOps Engineer (Grüße gehen raus 😉 ), war neu im Team.&lt;/p&gt;
&lt;p&gt;Auf seiner Webseite schreibt er &lt;em&gt;&amp;ldquo;Als Experte für Developer Experience habe ich das Ziel, ein Ökosystem für hochperformante &amp;amp; innovativ agierende Entwicklungsteams zu schaffen.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Und das lebt er. Es gibt auch mehr als genug zutun. &lt;strong&gt;Und wir wollen es anpacken!&lt;/strong&gt;&lt;/p&gt;</description><author>Backendhance</author><pubDate>Mon, 04 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/outch/</guid></item><item><title>OpenAI fixed their unsafe policy around names</title><link>https://ntietz.com/blog/openai-name-policy-safety-issue/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;&lt;strong&gt;Update October 2, 2023:&lt;/strong&gt; This is now fixed: you can update your name in &lt;a href="https://platform.openai.com/account/user-settings"&gt;your user settings&lt;/a&gt;.
This works for the OpenAI Platform accounts, and they say the same for ChatGPT (etc.) is coming soon.
Thank you to those who reached out to OpenAI employees about this, and thank you so much to the kind folks at OpenAI who I talked to who prioritized this and made it happen.
I've left this blog post up as a historical record.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update September 6, 2023:&lt;/strong&gt; A kind soul at OpenAI reached out to me and helped me get new accounts with my proper name.
I've also heard hints that there may be a solution to this coming; no details, but fingers crossed.
If you are facing a similar problem, you can get a new account with your proper name by going through support.
If you try and it doesn't work, you can &lt;a href="mailto:me@ntietz.com"&gt;email me&lt;/a&gt; and I will do my best to work some contacts to get you fixed up.
(&lt;em&gt;End of update.&lt;/em&gt;)&lt;/p&gt;
&lt;p&gt;I've &lt;a href="https://ntietz.com/blog/email-address-not-identifier/"&gt;written before&lt;/a&gt; about the challenges of changing my name and email address across platforms.
However, I have &lt;em&gt;not&lt;/em&gt; been able to update my name (or email) on my OpenAI accounts.
I have a personal account and a work account, and need the latter to do my job.
&lt;em&gt;This is actively harmful, and I want OpenAI to fix it&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Normally you can change your own name and email address, but with OpenAI, I can't edit my email or username on my account.
It's just not supported self-serve.
And I can't create a new account, since my email addresses and phone number have been used for the two account limit.&lt;/p&gt;
&lt;p&gt;OpenAI's official policy is that you cannot reuse an email nor can you reuse a phone number on a new account.
Here is what they say in one of their &lt;a href="https://help.openai.com/en/articles/6378407-how-to-delete-your-account"&gt;help articles&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Since every email address is unique per account, we require a different email address for new accounts.&lt;/p&gt;
&lt;p&gt;[...]&lt;/p&gt;
&lt;p&gt;New accounts are still subject to our limit of 2 active accounts per phone number. Deleted accounts do count toward this limit. Deleting an account does not free up another spot. A phone number can only ever be used up to 2 times for verification.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is part of their policy to prevent abuse and fraud, probably to prevent people from creating tons of accounts and using a lot of compute.
They have to do something, and limiting people to one or two accounts is fair, but the way they're doing it is the problem.&lt;/p&gt;
&lt;p&gt;So you'd think I could update my email address some other way, right?
If I can't edit it myself, and I can't create a new account, maybe they can update it for me.
Well, I asked their support for help udpating my name.
I'll give you one guess what they told me to do.&lt;/p&gt;
&lt;p&gt;Yup, &lt;em&gt;delete my account&lt;/em&gt; and create a new one.
Or add another email address to the API account, then remove the old one.&lt;/p&gt;
&lt;p&gt;What.
WHAT.&lt;/p&gt;
&lt;p&gt;The absolute cherry on top is that they told me by email on August 24th that "if you already have two accounts you’ll need to delete one of your existing accounts to free up your phone number".
In that response they link to &lt;a href="https://web.archive.org/web/20230823225856/https://help.openai.com/en/articles/6613520-phone-verification-faq"&gt;this help article&lt;/a&gt; (Wayback Machine link from the same date), which directly contradicts them and says that "previously deleted accounts still count toward our two accounts per number limit".&lt;/p&gt;
&lt;p&gt;Their only suggested solution for changing an email address or changing a name &lt;strong&gt;does not work&lt;/strong&gt; because their systems disallow it.
I don't know about you, but I don't have access to a pile of additional phone numbers to use.
I've got... one phone.
With one phone number.&lt;/p&gt;
&lt;p&gt;Not allowing name or email changes is an &lt;strong&gt;actively harmful policy&lt;/strong&gt;.
Here are some of the situations where you would want to change your name, and the ways it's harmful to disallow it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You leave an abusive partner whose name you had taken, and want to not see their last name everywhere. This would cause significant distress to have to see this often on an account.&lt;/li&gt;
&lt;li&gt;You are trans and you take a name that aligns with your gender identity. Seeing your deadname everywhere can cause significant distress and if this happened at work, could be part of contributing to a hostile work environment.&lt;/li&gt;
&lt;li&gt;You want to evade stalking. In this case, being unable to change it could make it easier to find you (one feature is sharing links to transcripts, which can include or omit your name; if you accidentally or intentionally share a link with your name, it could make it so you can be located more easily).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That's a few from the top of my head.
But ultimately, names are deeply personal and &lt;em&gt;not static&lt;/em&gt;, and it's a pretty bad move to not allow them to change.&lt;/p&gt;
&lt;p&gt;This policy has other holes in it, though, like what if you get a new phone number that someone else had used before for OpenAI.
Are you just out of luck, can't use their products?
This isn't a permanent solution.&lt;/p&gt;
&lt;p&gt;I've gone through their support and gotten nowhere.
I reached out to their data privacy officer on the slim hope of being able to correct my data and only got an automated response.
I don't know what else to do, except shout out into the internet and hope someone hears me.&lt;/p&gt;
&lt;p&gt;Please help me, and please stop hurting so many people who are in a similar situation to me.&lt;/p&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 04 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/openai-name-policy-safety-issue/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>AI: The Somnium Files review</title><link>https://burakku.com/blog/ai-the-somnium-files-review/</link><description>&lt;p&gt;&lt;img alt="AI: The Somnium Files" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Eye spy with my little AI.&lt;/p&gt;
&lt;p&gt;AI: The Somnium Files is an interactive murder mystery story. You play as Date Kaname, an amnesiac special agent trying to track down a mysterious serial killer using an AI-powered eyeball and a machine that allows invading the dreams of investigation targets to unlock the secrets of their minds by solving puzzles.&lt;/p&gt;
&lt;p&gt;But despite being a murder mystery, it kinda doesn't feel like one during a good chunk of it. The mood of it is kinda all over the place and quite a vast chunk of the game is spent engaging in various kinds of comedy routines. I imagine the humour won't land on everyone – it is quite Japanese and trends towards lower than higher end of the brow. I did generally like the comedic bits but it does induce a level of mood whiplash as you go from fawning over a porn magazine to gruesome murder in the span of 20 minutes. Thankfully it does manage to stay serious during the more serious and somber moments of the story, so it's not incredibly jarring.&lt;/p&gt;
&lt;p&gt;&lt;img alt="X-ray vision" src="xray.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;The Somnium Files features a relatively unique way of branching the storyline. Taking certain actions in the game puts you in a different branch in the story, and some of the branches lock you out in middle of it, telling you to go finish other branches to unlock it. It even unlocks a route, lets you read through it for a bit and then locks it up again in order to throw you onto another, now-unlocked route. At first I wasn't really sure what to think of it, but now in hindsight, I do like the implementation. It does a good job at keeping you guessing for a good chunk of the story, preventing you from reaching the juiciest bits first while simultaneously dropping hints across the different branches.&lt;/p&gt;
&lt;p&gt;As for the story itself, I really liked it. It's interesting, intriguing and engaging. It was fun to gather the clues throughout the story and work together the connections. And boy, are there connections. This thing is more interconnected than the World Wide Web. I'm proud that I was able to sleuth some of the plot points but I still didn't figure out the true culprit until the big reveal. As a novice to mysteries, the solution felt a bit out of left field, but not in a way that felt unsatisfying or unfair. And once you had the solution, a lot of the story prior to that just started clicking into places. Even some offhand remarks, made seemingly in jest, fit into the puzzle.&lt;/p&gt;
&lt;p&gt;Beyond just the murder mystery, I also enjoyed overall character stories. Most of the early endings don't end you with solving the crime but still had satisfying and emotional finales to them. I may have even shed a tear or two for this perverted detective adventure. That being said, the story isn't all that realistic. You can probably figure that out within minutes when they tell you that you have an artificial eyeball that contains a highly sophisticated artificial intelligence. So there's definitely a degree of sci-fi going on here. However, it also has other very unrealistic elements too for you to suspend your disbelief. You might have a little girl with the power to take down a bear with her bare hands, or have a squadron of gunmen shooting worse than the freaking Stormtroopers. But if this kind of a thing doesn't faze you, there's a fun and interesting story waiting for you.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Internet history" src="internet-history.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Most of the game is spent in interactive point-and-click segments where you explore the world and talk to the various characters. I don't think there's really any way to fail these, so it plays more like a visual novel with some leeway as to how many things you want to interact with. You also have some action bits with quick-time events and interrogation events where you need to pick evidence to show to a suspect to reel them in. The core gameplay is quite simple without really anything to particularly like or dislike, although I would've liked to be able to see what objects had new dialogue and which didn't. Could've saved me from clicking on Boss' computer for the ninth time to see if there was dialogue hidden behind it.&lt;/p&gt;
&lt;p&gt;The other part of the game, and arguably the most video game part of the whole thing, are the titular Somnium segments. These are puzzle segments where you need to figure out how to unlock the secrets of the target's mind before you run out of time. But counter to this description, these are relatively slow affairs. You have 360 seconds to spare and each action you can take inside the dream takes up an arbitrary amount of time, and idling in place happens in super-slow motion. Running towards a door and kicking it open might be just a second, or it might be closer to a minute, depending on the stage and conditions. And if you run out of time, you'll fail and have to replay through most if not all of it again. These dream sequences are also where the actual branching happens, so you won't need a walkthrough for routing.&lt;/p&gt;
&lt;p&gt;I'm a bit of two minds about the Somnium segments. On one hand, they were an interesting and unique aspect of the game, with the occasional moment of hilarity. And while some of the puzzles I could determine by remembering what clues I'd gathered during the normal gameplay and applying them in the dream, some of the aspects required just utterly pure guesswork. How am I supposed to know whether to stick my head or my hand inside a hole? And if you guess wrong and run out of time, you're gonna waste a bunch of time running through the same already-solved puzzles in order to progress through. These puzzles do definitely add to the game, but I just wish they didn't waste as much of my time if my guesses didn't work out. After a fail, it just becomes an exercise of mechanical repetition and holding down the skip button.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Oh hey, me too." src="internet-famous.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;The graphics are so-so. The world detail leaves a lot of room for improvement but the character models are mostly good with excellent designs. Some of the facial animations look a bit iffy at times, but I did quite like the overall 3D character look. There's a sense of charm about it. I wasn't necessarily expecting that since I've usually preferred 2D sprites over 3D models in other games. Iris and Mizuki were both very cute in the game. And of course, with everyone being a 3D model, you could also have properly animated cutscenes in the game. Well, cutscene animation was also a bit strange at times too, but it's still a more visual affair than most visual novels.&lt;/p&gt;
&lt;p&gt;No complaints on the sound side. Opting for the original Japanese voices, the voice acting was pretty good. Not its greatest asset but still good. The soundtrack was nothing special but it did its job providing the mood.&lt;/p&gt;
&lt;p&gt;The technical side however is a bit sketchy. This is quite clearly a console port and has some basic graphics options inside a separate launcher that don't all even work properly. It also forces you to choose between keyboard/mouse and controller input in the launcher, even though you can still use a controller with keyboard/mouse controls. You just don't get the proper button prompts for a controller. Annoyingly it also ignored my choice for Japanese voice acting for the ending movie for reasons unknown. Granted, it never crashed on me or anything, but a bit more polish on the technical front would've been nice.&lt;/p&gt;
&lt;p&gt;I would definitely recommend reading through AI: The Somnium Files. Its gameplay won't win it any recognition but story is interesting, rather fun, occasionally touching and most likely will keep you guessing until the end. I'm definitely going to be checking out the sequel, &lt;em&gt;AI: The Somnium Files - Nirvana Initiative&lt;/em&gt;, at some point. I imagine I also need to check out other Uchikoshi production too.&lt;/p&gt;</description><author>ブラック</author><pubDate>Mon, 04 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/ai-the-somnium-files-review/</guid></item><item><title>Practice and Experience</title><link>https://blog.bayindirh.io/blog/practice-and-experience/</link><description>&lt;p&gt;During my lifetime, I have met with people who had certain skills, like reading the weather, or knowing a technical subject inside out, or being able to learn pretty quickly while being thorough. I have admired these people, and wanted to learn what made them tick. The main question was "How is this possible, and what can I learn from these people", and the feeling accompanying this question was always a healthy wonder and awe, not envy or anger.&lt;/p&gt;
&lt;p&gt;Moreover, I noticed that these people didn't boast their skills, in any way. A neighbor may make a passing remark about tomorrow's weather, or a friend will casually note a pitfall I may encounter during implementing something. Moreover, things progress exactly the way they noted earlier. This like being among a band of oracles, and both feels awesome and makes you wonder about how this is possible.&lt;/p&gt;
&lt;p&gt;As life progressed, and I tried to quench my thirst for learning, a pattern started to emerge. I noticed that I carry a big body of knowledge about a couple of subjects, without trying for it. Interestingly, the dialogues would progress the same. A casual remark about a potential problem followed by a "I didn't think of this" look. These occurrences surprised me, generating more questions about the process, converting main question to "How I did this?"&lt;/p&gt;
&lt;p&gt;The question prompted me to observe myself more intently and closely.&lt;/p&gt;
&lt;p&gt;This observation lead me to two revelations about the process. i.e. How practice and experience works, and how adding time to these changes things.&lt;/p&gt;
&lt;p&gt;The first revelation was how practice and experience work in tandem. It's always said that "practice makes perfect", and to perfect ourselves, we practice. Practice is irreplaceable and invaluable all in itself. It sharpens the skill and the mind, reducing the mental cost of the skill while increasing the quality of the result. However, it's the experience you gain adds the subtle and impeccable parts to your knowledge and how you handle things. The edge cases, possible problems, the smell of how things are going can't be obtained without experience. A textbook, a mentor, or a course may give you a direction or a simple sense, but sharpening that is up to you and it's only possible with the experience you get over time. Because, even if you practice deliberately, you sharpen what you know. What you don't know can't be practiced.&lt;/p&gt;
&lt;p&gt;However, experience does not make you better at that instant. I t needs time. Time is required to let the experience sink in, get introduced into your practice, and get considered during the next real-world scenario requires that knowledge. When you take that step, and get experience on top of the existing one, because you need to go step by step, the knowledge will start to accumulate.&lt;/p&gt;
&lt;p&gt;This accumulation is slow and not recognizable, but when you need to tap into that knowledge, it'll show itself.&lt;/p&gt;
&lt;p&gt;Until next time,&lt;/p&gt;
&lt;p&gt;Be kind.&lt;/p&gt;</description><author>bayindirh</author><pubDate>Mon, 04 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.bayindirh.io/blog/practice-and-experience/</guid></item><item><title>Paths are Made by Walking</title><link>https://jdsalaro.com/bookmark/quote/kafka-nietzsche-antonio-machado-paths-wege/</link><description>paths are made by walkingThis aphorism is also found in German as follows:Wege entstehen dadurch, dass wir sie gehen</description><author>Jayson Salazar Rodriguez | @jdsalaro | Blog</author><pubDate>Mon, 04 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jdsalaro.com/bookmark/quote/kafka-nietzsche-antonio-machado-paths-wege/</guid></item><item><title>Eight years of organizing tech meetups</title><link>http://notes.eatonphil.com/eight-years-of-tech-meetups.html</link><description>&lt;p&gt;This is a collection of random personal experiences. So if you don't
want to read everything, feel free to skip to the end for takeaways.&lt;/p&gt;
&lt;p&gt;I write because I'd like to see more high-quality meetups. And maybe
my little bit of experience will help someone out.&lt;/p&gt;
&lt;h3 id="2015:-philadelphia"&gt;2015: Philadelphia&lt;/h3&gt;&lt;p&gt;I first tried to organize a meetup in Philly in 2015. I was
contracting at the time and I figured a meetup might be a good way to
source contracts or just meet interesting people. I created the
"Philadelphia Software in Business" (or some other similarly vaguely
named) group on Meetup.com.&lt;/p&gt;
&lt;p&gt;I didn't have any network; the first companies I worked for were not
in Philly. But Meetup.com got me a few tens of people joining the
group.&lt;/p&gt;
&lt;p&gt;My first challenge was finding a place to meet. I didn't know what I
was doing so I looked at restaurants, bars, and cafes for dedicated
event space. Needless to say, renting space was expensive on its
own. And there was always an additional required minimum dollar spent
per attendee.&lt;/p&gt;
&lt;p&gt;I ultimately found a place near the Schuylkill River. Maybe it was a
community event space. Maybe I paid for it. I can't remember.&lt;/p&gt;
&lt;p&gt;The first and only time I hosted an event for the group, I got a
surprising number of people for such a vague topic. There were maybe 6
of us. I was the youngest by far (I was 20), they were middle
age. Excel users and one visionary type.&lt;/p&gt;
&lt;p&gt;There was no real point to the meetup and I didn't continue doing
it.&lt;/p&gt;
&lt;h3 id="2016---2017:-linode"&gt;2016 - 2017: Linode&lt;/h3&gt;&lt;p&gt;While I was at Linode, I organized "hack nights". I didn't ask for
anyone's approval before starting it. I just said I'd be ordering
pizza for anyone interested in staying after work to hack on
Linode-related projects. I was willing to pay for the pizza, in part
because I didn't want to risk being shut down by asking. But caker
paid for it each time.&lt;/p&gt;
&lt;p&gt;I was nervous because people would show up and ask for pizza and not
want to hack. It was company-provided under the aspiration of doing
Linode-related work. Maybe I mentioned this or not. I can't
remember. I'm pretty sure they got their pizza.&lt;/p&gt;
&lt;p&gt;Aside from myself, developers at Linode didn't really attend. The
folks who attended were support staff or folks from the technical
writing team who wanted more experience coding.&lt;/p&gt;
&lt;p&gt;I ran this for maybe 3 to 5 Wednesdays before not continuing. It was
pretty fun! But staying after work for a few hours each Wednesday lost
its charm.&lt;/p&gt;
&lt;h4 id="book-club"&gt;Book Club&lt;/h4&gt;&lt;p&gt;Another time at Linode I started a book club. I was very torn about
attempting to make the book club open to anyone in the area or just to
Linode employees.&lt;/p&gt;
&lt;p&gt;I knew I'd probably get more people to attend if I made it public. But
I wasn't sure if Linode would be cool with having external folks in
the office. Before they moved to the Old City office, visitors weren't
really a thing.&lt;/p&gt;
&lt;p&gt;So I made it private to Linode. And I started with the most obvious
book for your average developer: Practical Common Lisp.&lt;/p&gt;
&lt;p&gt;I am pretty sure I learned one big trick by this time though. When I
announced I'd be starting the book club I said something like this:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Hey folks! I'm thinking of starting a book club. A book I have in
mind to start with is Practical Common Lisp. If I get at least one
other person to join in then I'll move forward!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I ended up getting two folks: one developer and one support staff
member. We held the book club for 30 minutes once a week, covering one
chapter each week. I was the only one who read anything I think, but
the other two guys faithfully showed up for discussion.&lt;/p&gt;
&lt;p&gt;I didn't ask for permission to do this either. And this time we met
during company time. I think it was 2-2:30PM.&lt;/p&gt;
&lt;p&gt;It was fun. We finished the book. But Practical Common Lisp probably
wasn't a good choice. And I don't think I started a second book.&lt;/p&gt;
&lt;h3 id="2017---2020:-false-starts"&gt;2017 - 2020: False starts&lt;/h3&gt;&lt;p&gt;I moved to NYC and joined a small startup (~20 employees). Linode was
100+ employees.&lt;/p&gt;
&lt;p&gt;We were in a WeWork so I considered starting a book club that was
public to the WeWork. I had learned by then the law of numbers: I
probably wouldn't get anyone from my company to join.&lt;/p&gt;
&lt;p&gt;I considered putting up posters around the WeWork to advertise. But in
the end, I didn't end up going through with anything.&lt;/p&gt;
&lt;p&gt;I did present at a few meetups in NYC during this time. But I didn't
organize anything.&lt;/p&gt;
&lt;p&gt;And then the pandemic hit and everything disappeared.&lt;/p&gt;
&lt;h3 id="2021---2022:-virtual"&gt;2021 - 2022: Virtual&lt;/h3&gt;&lt;p&gt;In 2021 I started contracting again, thinking about starting a
company. I wanted a community to be at the center.&lt;/p&gt;
&lt;p&gt;So I started a &lt;a href="https://eatonphil.com/discord.html"&gt;Discord focused on software
internals&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I had a bit more of a network at this point so I posted about the
Discord on Twitter and got 100 likes or something and slowly started
gaining folks in the Discord.&lt;/p&gt;
&lt;p&gt;I knew it was going to do better if I was pretty active in it so I
made sure to post interesting blog posts at some regular
interval. About compilers or databases or something.&lt;/p&gt;
&lt;p&gt;The Discord didn't turn out to help me out much in the
starting-a-company front. Or I didn't use it effectively for that.&lt;/p&gt;
&lt;p&gt;I wanted more of an independent Discord of cool people who like to
learn about systems internals. And that's what I got.&lt;/p&gt;
&lt;p&gt;This turned out to be ok though because I stopped working on that
company and the Discord is still around and I still get to hang out
with cool people.&lt;/p&gt;
&lt;p&gt;This Discord is still around and hit 1,700 members recently. Among
other things, it has developers from many different database companies
in it these days. They hang out and help out the noobs like me learn
about database internals.&lt;/p&gt;
&lt;p&gt;I culled inactive members recently, so today the total is around
1,100.&lt;/p&gt;
&lt;h4 id="hacker-nights"&gt;Hacker Nights&lt;/h4&gt;&lt;p&gt;During the pandemic I became frustrated that all the good meetups
disappeared so I decided to start an online one that would be somewhat
tied to the Discord and be about software internals.&lt;/p&gt;
&lt;p&gt;I would find 2 or 3 people to present for 10-20 minutes each on
anything to do with software internals. We'd meet once a month at 8PM
NY time I think.&lt;/p&gt;
&lt;p&gt;To get speakers I'd mostly DM people who I saw do interesting things
on Twitter or Hacker News. I was lucky to have &lt;a href="https://www.philipotoole.com/"&gt;Philip
O'Toole&lt;/a&gt; (author of rqlite), &lt;a href="https://sirupsen.com/"&gt;Simon
Eskildsen&lt;/a&gt; (author of the Napkin Math blog),
&lt;a href="https://rsms.me/"&gt;Rasmus Andersson&lt;/a&gt;, and many other excellent folks
speak.&lt;/p&gt;
&lt;p&gt;You can find &lt;a href="https://www.youtube.com/playlist?list=PL2t91m2Rvccpg2q2o_8lfuTYUhoP3AMwq"&gt;videos of these talks on
YouTube&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The events were organized on Meetup.com. The group grew quickly
and I'd have about 100 people RSVP to each event. 10-20 normally
showed up.&lt;/p&gt;
&lt;p&gt;I'd post a Zoom link on Meetup.com. Sometimes Meetup.com crashed right
as the meetup started, so no one could get a link. That was fun.&lt;/p&gt;
&lt;p&gt;On two different nights I had Zoom bombers show up and play crazy
music or impersonate other members of the call and act weirdly (Zoom
lets you change your name after you've joined the call).&lt;/p&gt;
&lt;p&gt;I learned a little bit about how to administrate a Zoom meeting.&lt;/p&gt;
&lt;p&gt;I ran Hacker Nights for 5 months. It was tiring to
find speakers, tiring to deal with Zoom bombers. It was thankless and
I wasn't really enjoying it.&lt;/p&gt;
&lt;p&gt;I was proud though that I was offering a channel for developers to
learn about software internals of compilers, databases, etc. And it
was great to meet many interesting speakers and attendees.&lt;/p&gt;
&lt;h3 id="2023:-designing-data-intensive-applications"&gt;2023: Designing Data Intensive Applications&lt;/h3&gt;&lt;p&gt;A month ago I put out a call on Twitter for folks in NYC interested in
reading through the book Designing Data Intensive Applications.&lt;/p&gt;
&lt;p&gt;I'd read the book before and while it was challenging, I knew it was
immensely useful to any developer who works with data or an API.&lt;/p&gt;
&lt;p&gt;By this time I'd learned my second trick: not asking for public
responses.&lt;/p&gt;
&lt;p&gt;I said something like:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Hey folks! I'm thinking of starting a book club meeting in Midtown
NYC reading through Designing Data Intensive Applications. DM me if
you'd be interested! If I get 2 other interested folks this will be on!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I got maybe 40 DMs and 20 of them were based in NYC. Attendence thus
would have been higher if I made the book club virtual. But virtual
events take about as much effort as in-person events and somehow feel
less rewarding. So I went through with the NYC group.&lt;/p&gt;
&lt;p&gt;I'm sure I could have gotten some company to provide us space, but
this would just mean more negotation for me and tedium for everyone
involved (bring your ID to be checked in, make sure you're registered,
etc.).&lt;/p&gt;
&lt;p&gt;The group would meet every 2 weeks and cover 2 chapters at a
time. We'd meet for 30 minutes. To avoid needing to find a place to
meet, we'd meet in public at Bryant Park. (There turns out to be
plenty of available seating on Fridays at 9AM in Bryant Park. When it
rains we meet online.)&lt;/p&gt;
&lt;p&gt;I wanted to keep the overhead minimal and the timeline slightly
aggressive. We'd be through the book in only 3 months. No crazy
commitment.&lt;/p&gt;
&lt;p&gt;We've meet twice now and are 25% done the book. Attendance has been
around 7 to 9 people each time so far, or a little less than 50%.&lt;/p&gt;
&lt;p&gt;They're almost all software developers, with one manager I think, who
work for a variety of large and small tech companies.&lt;/p&gt;
&lt;p&gt;I'm loving it so far. And if it continues to go well, I'll probably
continue running in-person book clubs.&lt;/p&gt;
&lt;p&gt;But it would only meet a few months a year, giving me a few month
breaks from running it.&lt;/p&gt;
&lt;h3 id="takeaways:-the-meh"&gt;Takeaways: The meh&lt;/h3&gt;&lt;p&gt;Organizing any event takes effort. Meetups are especially hard because
you need to find a place to run the meetup, you probably want to
provide food, and you need to find speakers.&lt;/p&gt;
&lt;p&gt;Often you can find a single place to host the meetup, but you have to
constantly search for new speakers. Even one of the greatest meetups
in NYC, Papers We Love, seems to be struggling to find speakers.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://db.cs.cmu.edu/seminar2023/"&gt;CMU Database Group&lt;/a&gt; and the
&lt;a href="http://charap.co/category/reading-group/"&gt;Distributed Systems Reading
Group&lt;/a&gt; seem to have the
right idea though. They only run sessions part of the year, and they
plan out all sessions in advance (including speakers).&lt;/p&gt;
&lt;p&gt;However, they are both virtual. And I'm not so interested in running
virtual events anymore.&lt;/p&gt;
&lt;h3 id="takeaways:-the-good"&gt;Takeaways: The good&lt;/h3&gt;&lt;p&gt;For one, meetups are an awesome way to meet random people and expand
your network.&lt;/p&gt;
&lt;p&gt;Two, they're educational. Even beyond the content you are meeting
about, there's the discussion alongside it you wouldn't get by
yourself. And you, as organizer, get to pick the topic.&lt;/p&gt;
&lt;p&gt;These work out great for me. I love to meet people, and I love to
learn.&lt;/p&gt;
&lt;h4 id="tricks"&gt;Tricks&lt;/h4&gt;&lt;p&gt;Starting something new is embarassing because you're putting yourself
out there. Maybe no one in your network shares your interests (to the
degree or in the direction you do).&lt;/p&gt;
&lt;p&gt;My tricks are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Most importantly: keep things low key! Don't stress people
out. Before learning and networking, the point of meetups is (or should
be) fun.&lt;/li&gt;
&lt;li&gt;Saying you are "thinking about X" is a lightweight way to gauge
interest. As compared to just saying you're "starting X", which gives
you less room to back out if there turns out not to be interest.&lt;/li&gt;
&lt;li&gt;Asking people to DM you with interest is less embarrassing than
asking for people to respond in public. Not everyone would want to
respond in public. If there's interest in private, you can share the
interest in public later on. But if you only ask for responses in
public and there are no responses, that can feel embarassing.&lt;/li&gt;
&lt;li&gt;Indicating success criteria can help people understand how big
you're thinking of. I'm normally fine with doing something as small
as only two other people, so I say that. It's kind of like how
Kickstarters work with minimum funding levels.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These ideas apply to corporate planning too. I think about them when
I'm sharing some new idea in company Slack as much as when I share on
Twitter.&lt;/p&gt;
&lt;p&gt;A note on attendance rates: 10-20% actual attendance versus RSVP seems
normal. If you get a higher percentage of people actually attending
versus RSVP-ing you're doing pretty well!&lt;/p&gt;
&lt;h4 id="finding-sponsors"&gt;Finding sponsors&lt;/h4&gt;&lt;p&gt;One final idea is about paying for space or paying for food. Companies
with space and money for food are often willing to partner with folks
willing to do the work to run an event.&lt;/p&gt;
&lt;p&gt;Running your own event in a company's space is advertising for
them. They get to be associated with cool tech. It's a chance for them
to pitch their open positions.&lt;/p&gt;
&lt;p&gt;Obviously this happens often when you start a meetup hosted by your
own company. But you can also find other companies to host space.&lt;/p&gt;
&lt;p&gt;The kind of people to find to make this happen are senior
developers or engineering managers, often on Twitter and sometimes on
LinkedIn.&lt;/p&gt;
&lt;p&gt;I haven't done this myself yet because I'm not ready to commit to
running a meetup. But I see it happen. And it's the approach I'd take
if I were to run a real meetup again.&lt;/p&gt;
&lt;p&gt;Though now that I've got some time off there are a few talks I'd like
to do myself.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Wrote a post on my experience organizing tech meetups of various stripes over the years. And a few things I've learned.&lt;br /&gt;&lt;br /&gt;&amp;quot;meetups&amp;quot; taken pretty broadly to include online communities, book clubs, and actual speaker events.&lt;a href="https://t.co/xnd0LTneup"&gt;https://t.co/xnd0LTneup&lt;/a&gt; &lt;a href="https://t.co/w1oEaSNDHb"&gt;pic.twitter.com/w1oEaSNDHb&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1698793650036031753?ref_src=twsrc%5Etfw"&gt;September 4, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Mon, 04 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/eight-years-of-tech-meetups.html</guid></item><item><title>Running Tarsnap</title><link>https://www.ashish.zip/2023/09/running-tarsnap.html</link><description>&lt;p&gt;&amp;nbsp;This post is to document the procedure to run Tarsnap on MacOS. Tarsnap is basically an online service that runs on top of Amazon cloud infrastructure for online backup. If you are reading this than you already know much about it but needless to say it is a cool program.&lt;/p&gt;&lt;p&gt;First thing, we need to install it which can be easily accomplished using homebrew.&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: Inconsolata;"&gt;brew install tarsnap&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: inherit;"&gt;It can also be installed by compiling it from source but it is outside the scope of this post.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Once installed, it is a good idea to run the dry-stat command to see how much space it will take and what is the compression ratio. It is supposed to de-duplicate the data and store the those unique bytes.&amp;nbsp;&lt;/p&gt;&lt;p&gt;For example, to see if we want to only upload pdf and .Rdata files from "Analysis" folder, we can run the following command.&amp;nbsp;&lt;/p&gt;&lt;span style="font-family: verdana;"&gt;&lt;code&gt;find /Users/xyz/Analysis -type f  \( -name '*.pdf' -o -name '*.Rdata' \) -print0 | tarsnap --dry-run --no-default-config --print-stats --humanize-numbers -c --null -T- &lt;/code&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: inherit;"&gt;Now this command is doing lot of things, first it is "finding" files with "&lt;span&gt;-type f" &lt;/span&gt;&lt;span&gt;and then finds only files &amp;nbsp;ending with Rdata and pdf extension. Notice the use of -o to indicate "or" operator within find command. If we have more than two file type extensions, then we need to use parenthesis to enclose all the files types. We are using "-print0" to separate the filenames using the null character so it won't fail with some &lt;/span&gt;weird&lt;span&gt;&amp;nbsp;characters in the filenames. This is then piped to the tarsnap program. &lt;/span&gt;The keywords here are --null to account for the passing files with "-print0" option.&amp;nbsp;&lt;/span&gt;&lt;div&gt;&lt;blockquote style="border: medium; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp;--null &amp;nbsp;(use with -I, -T, or -X) Filenames or patterns are separated by&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;null characters, not by newlines. &amp;nbsp;This is often used to read&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;filenames output by the -print0 option to find(1).&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote style="border: medium; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"&gt;&lt;p style="text-align: left;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;-T filename&lt;/p&gt;&lt;p style="text-align: left;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;(c, x, and t modes only) In x or t mode, tarsnap will read the&lt;/p&gt;&lt;p style="text-align: left;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;list of names to be extracted from filename. &amp;nbsp;In c mode, tarsnap&lt;/p&gt;&lt;p style="text-align: left;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;will read names to be archived from filename. &amp;nbsp;The special name&lt;/p&gt;&lt;p style="text-align: left;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;“-C” on a line by itself will cause the current directory to be&lt;/p&gt;&lt;p style="text-align: left;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;changed to the directory specified on the following line. &amp;nbsp;Names&lt;/p&gt;&lt;p style="text-align: left;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;are terminated by newlines unless --null is specified. &amp;nbsp;Note that&lt;/p&gt;&lt;p style="text-align: left;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;--null also disables the special handling of lines containing&lt;/p&gt;&lt;p style="text-align: left;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;“-C”. &amp;nbsp;If filename is “-” then the list of names will be read&lt;/p&gt;&lt;p style="text-align: left;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;from the standard input. &amp;nbsp;Note: &amp;nbsp;If you are generating lists of&lt;/p&gt;&lt;p style="text-align: left;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;files using find(1), you probably want to use -n as well.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;so the "-" following the "-T" option allows to pass the name using std-in via find command. Once you run the command, we should see output like this.&amp;nbsp;&lt;/p&gt;&lt;blockquote style="border: medium; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"&gt;&lt;blockquote style="border: medium; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"&gt;&lt;p&gt;tarsnap: Removing leading '/' from member names&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote style="border: medium; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;Total size &amp;nbsp;Compressed size&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote style="border: medium; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"&gt;&lt;p&gt;All archives &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 8.4 MB &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 3.4 MB&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote style="border: medium; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"&gt;&lt;p&gt;&amp;nbsp; (unique data) &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;8.4 MB &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 3.4 MB&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote style="border: medium; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"&gt;&lt;p&gt;This archive &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 8.4 MB &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 3.4 MB&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote style="border: medium; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"&gt;&lt;p&gt;New data &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 8.4 MB &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 3.4 MB&lt;/p&gt;&lt;/blockquote&gt;&lt;/blockquote&gt;&lt;p&gt;Now this is just the test. In order to run the Tarsnap, we need to register for an account and get ready for some configuration for which we need:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ol style="text-align: left;"&gt;&lt;li&gt;tarsnap.conf&lt;/li&gt;&lt;li&gt;tarsnap.key&lt;/li&gt;&lt;li&gt;setting cache directory&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;If you installed tarsnap simply using homebrew than the location of tarsnap.conf will be in /opt/homebrew/etc so just copy that file to your home directory using&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;cp /opt/homebrew/etc/tarsnap.conf.sample ~/tarsnap.conf&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;According to the documentation (https://www.tarsnap.com/gettingstarted.html#configuration-file),&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;p&gt; If you would prefer to run Tarsnap as a normal user, &lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote style="border: medium; margin: 0px 0px 0px 40px; padding: 0px;"&gt;&lt;p style="text-align: left;"&gt;&lt;span style="font-family: Inconsolata;"&gt;&amp;nbsp;cp /opt/homebrew/etc/tarsnap.conf.sample&amp;nbsp;~/.tarsnaprc&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;p&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;since we will be running it as normal user, we use this above alternative. Now for the "tarsnap.key", it is generated as part of the registration of the computer with tarsnap server.&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;blockquote style="border: medium; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"&gt;&lt;p&gt;&lt;span style="font-family: Inconsolata;"&gt;sudo tarsnap-keygen \&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: Inconsolata;"&gt;&amp;nbsp; &amp;nbsp; --keyfile /Users/xyz/tarsnap.key \&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: Inconsolata;"&gt;&amp;nbsp; &amp;nbsp; --user me@example.com \&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: Inconsolata;"&gt;&amp;nbsp; &amp;nbsp; --machine mybox&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Make sure the key can be read by the user otherwise it won't work.&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: Inconsolata;"&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;sudo chmod 0444&amp;nbsp;tarsnap.key&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;Now let us set the cache directory&lt;/p&gt;&lt;p style="text-align: left;"&gt;&lt;span style="font-family: Inconsolata;"&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;sudo chmod 700 /Users/xyz/tarsnap_cache&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style="text-align: left;"&gt;&lt;span style="font-family: Inconsolata;"&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style="text-align: left;"&gt;&lt;span style="font-family: inherit;"&gt;Make sure to point your .tarsnaprc file to the location of the key and cachedir.&amp;nbsp;&lt;/span&gt;&lt;span style="font-family: inherit;"&gt;If it all goes write, then use this command to actually run the real backup:&lt;/span&gt;&lt;/p&gt;&lt;p style="text-align: left;"&gt;&lt;span style="font-family: Inconsolata;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style="text-align: left;"&gt;&lt;span style="font-family: Inconsolata;"&gt;&lt;code&gt;find /Users/xyz/Analysis &amp;nbsp;-type f \( -name '*R' -o -name "*.pdf" -o -name "*.Rdata" \) &amp;nbsp;-print0 | tarsnap &amp;nbsp;--print-stats &amp;nbsp;-c -f "analysis_back-$(date +%Y-%m-%d_%H-%M-%S)" --null -T- &lt;/code&gt; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p style="text-align: left;"&gt;&lt;span style="font-family: Inconsolata;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style="text-align: left;"&gt;&lt;span style="font-family: inherit;"&gt;Notice we added&amp;nbsp;$(date +%Y-%m-%d_%H-%M-%S) to note the date of the archive. Tarsnap won't allow us to back with the same archive name. It won't let us delete the archive unless explicitly told to do so.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p style="text-align: left;"&gt;&lt;span style="font-family: inherit;"&gt;we can list the archives &lt;/span&gt;using&lt;span style="font-family: inherit;"&gt;&amp;nbsp;this command:&lt;/span&gt;&lt;/p&gt;&lt;blockquote style="border: medium; margin: 0px 0px 0px 40px; padding: 0px;"&gt;&lt;p style="text-align: left;"&gt;&lt;span style="font-family: Inconsolata;"&gt;tarsnap --list-archives&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p style="text-align: left;"&gt;&lt;span style="font-family: inherit;"&gt;&lt;span&gt;Now we can set up launchd https://web.archive.org/web/20230627074009/https://www.launchd.info/&lt;/span&gt;&lt;span&gt;&amp;nbsp;to run that command every week or every day as needed. Hopefully, this will help someone who is looking to set tarsnap on MacOS. That someone will be mostly likely me.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style="text-align: left;"&gt;&lt;span style="font-family: Inconsolata;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style="text-align: left;"&gt;&lt;span style="font-family: Inconsolata;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style="text-align: left;"&gt;&lt;span style="font-family: Inconsolata;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;</description><author>Thoughts and ideas</author><pubDate>Mon, 04 Sep 2023 00:26:35 GMT</pubDate><guid isPermaLink="true">https://www.ashish.zip/2023/09/running-tarsnap.html</guid></item><item><title>Vector search for dummies</title><link>https://philbooth.me/blog/vector-search-for-dummies/</link><description>&lt;p&gt;Recently I built a system
that uses vector search to logically truncate long documents
and retain the most significant parts
according to some search term.
I&amp;rsquo;m a dummy,
with no background in machine learning or mathematics,
so there were new concepts for me to understand
and implementation details to figure out.
This post summarises what I learned.&lt;/p&gt;
&lt;h3 id="what-is-vector-search"&gt;What is vector search?&lt;/h3&gt;
&lt;p&gt;Vector search is a way to find stuff
by what it &lt;em&gt;means&lt;/em&gt;,
not what it &lt;em&gt;says&lt;/em&gt;.
Conventional search works by looking for similarity
between text in a query and text in the search space.
Vector search works by mapping text
to an alternative representation that models similarity of meaning
and then searching those alternative representations
instead of the text itself.
The alternative representation is called a vector.&lt;/p&gt;
&lt;p&gt;For a very basic example, say you search for &amp;ldquo;dog&amp;rdquo;.
A document about spaniels that doesn&amp;rsquo;t include the word &amp;ldquo;dog&amp;rdquo;
would be found by vector search,
but not by conventional search.&lt;/p&gt;
&lt;p&gt;The relationships that vector search can identify
are surprisingly powerful;
questions can be matched to their answers,
translations can be made between different languages,
cultural references can be interpreted.&lt;/p&gt;
&lt;h3 id="what-is-a-vector"&gt;What is a vector?&lt;/h3&gt;
&lt;p&gt;A vector is an array of floating point numbers,
representing a position in &lt;em&gt;n&lt;/em&gt;-dimensional space.
For example,
if you&amp;rsquo;re programming a 2d game,
the &lt;em&gt;x&lt;/em&gt; and &lt;em&gt;y&lt;/em&gt; co-ordinates are a 2-dimensional vector.
Whereas if you&amp;rsquo;re programming a 3d game,
the &lt;em&gt;x&lt;/em&gt;, &lt;em&gt;y&lt;/em&gt; and &lt;em&gt;z&lt;/em&gt; co-ordinates are a 3-dimensional vector.&lt;/p&gt;
&lt;p&gt;The vectors that you&amp;rsquo;ll encounter in vector search
are much larger than 2 or 3 dimensions,
but the principle is the same.
The length of any vector is its &lt;em&gt;dimensionality&lt;/em&gt;,
so a vector of length 512 is said to have 512 &lt;em&gt;dimensions&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id="but-what-do-vectors-have-to-do-with-search"&gt;But what do vectors have to do with search?&lt;/h3&gt;
&lt;p&gt;Consider the 2-dimensional &lt;em&gt;x&lt;/em&gt; and &lt;em&gt;y&lt;/em&gt; co-ordinates mentioned above.
We can measure how far an object is from a given position
using those co-ordinates
and some basic geometry you probably remember from school.
Pythagoras&amp;rsquo; theorem
&lt;code&gt;a&lt;sup&gt;2&lt;/sup&gt; + b&lt;sup&gt;2&lt;/sup&gt; = c&lt;sup&gt;2&lt;/sup&gt;&lt;/code&gt;
establishes that you can calculate the distance between two points on a plain
by summing the squares of their distance along orthogonal axes,
&lt;em&gt;x&lt;/em&gt; and &lt;em&gt;y&lt;/em&gt; in this case,
then taking the square root.
The same formula can be applied to vectors with greater dimensions
and this is one way to measure vector similarity,
called &lt;em&gt;Euclidean distance&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Fundamentally,
search is the process of finding items
that are &lt;em&gt;closest&lt;/em&gt; to a query.
By mapping text to vectors
so that similar meanings or ideas produce similar vectors,
we turn semantic search into a mathematical problem.&lt;/p&gt;
&lt;h3 id="how-are-vectors-generated"&gt;How are vectors generated?&lt;/h3&gt;
&lt;p&gt;At this point,
you might be thinking that reliably generating vectors
for any text sounds like the hardest part of all this.
And you&amp;rsquo;d be right,
but fortunately it&amp;rsquo;s a solved problem.&lt;/p&gt;
&lt;p&gt;Vectors can be generated using a large language model (LLM),
the same technology that powers AI assistants like ChatGPT.
LLMs are able do this because
vectors are a data structure they use internally to model language.
In LLM parlance,
vectors are often referred to as &lt;em&gt;embeddings&lt;/em&gt;.
I&amp;rsquo;ll use those two terms interchangeably
for the remainder of this post.&lt;/p&gt;
&lt;p&gt;Of course,
the models that are used for chat completion
are not the same ones used to generate embeddings.
Instead there are specific models
built especially for the purpose of turning text into vectors.
There are many examples of these models out there,
with different tradeoffs relating to size and speed.
I recommend building an architecture
that lets you plug in different models easily,
so you can play with various options
and get a sense of what&amp;rsquo;s best for your application.&lt;/p&gt;
&lt;p&gt;Each model will always generate the same dimensionality of vector,
regardless of input text size.
So it follows that embeddings are &lt;em&gt;lossy&lt;/em&gt;;
if you turn a very large document
into a much shorter vector,
you&amp;rsquo;ve lost some information.
For this reason,
it&amp;rsquo;s important to break long documents into chunks
before sending them to your embedding model.&lt;/p&gt;
&lt;p&gt;The size of the chunks will directly affect
the accuracy and precision of your search implementation.
There are no hard and fast rules concerning the &amp;ldquo;best&amp;rdquo; chunk size,
so experiment aggressively within the context of your application.
I&amp;rsquo;ve seen it suggested elsewhere
that a reasonable starting point is 4kb chunks,
with a 10% overlap to preserve context around breakpoints.
Depending on your application,
you may also want to track the origin of each chunk
within its parent document;
e.g. chapters in a book or sections in a page.&lt;/p&gt;
&lt;h3 id="how-are-vectors-searched"&gt;How are vectors searched?&lt;/h3&gt;
&lt;p&gt;For vector search to work,
all of the documents from your search space
must be converted to vectors and added to an index.
With that in place,
you can convert each search query to a vector
and then find the items closest to it.&lt;/p&gt;
&lt;p&gt;Indexing and search are typically implemented by a data store or search engine,
although there are in-memory implementations available too.
Picking the right implementation depends entirely on your usage.
If you&amp;rsquo;re working with ephemeral data of moderate size,
an in-memory implementation might suit you best.
If you need persistence and already have a data store in your backend,
it may support vector search either in its core or via a plugin.
There are also dedicated vector databases you can use.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m deliberately not identifying any specific data stores or libraries in this post,
for two reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;I&amp;rsquo;m not an expert
and there are far more qualified opinions than mine
that can offer good advice.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The state of the art is evolving quite rapidly
and whatever I write here will likely become outdated very soon.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That said,
in my experience most implementations
support multiple different algorithms
and have many options you can tweak to configure performance.
Picking the right implementation is probably more about
fitting nicely with your existing infrastructure,
than it is about agonising over the &amp;ldquo;right&amp;rdquo; one.&lt;/p&gt;
&lt;h3 id="what-are-some-common-search-options"&gt;What are some common search options?&lt;/h3&gt;
&lt;p&gt;There are two main concepts at play
when it comes to searching vectors.&lt;/p&gt;
&lt;p&gt;The first one is distance,
which I discussed a bit earlier.
I mentioned &lt;em&gt;Euclidean distance&lt;/em&gt;,
which is the simplest way to measure vector similarity
but not the best at finding similar text,
because it can be thrown off by magnitude differences
when short queries are compared
to much longer chunks.
Instead, &lt;em&gt;cosine similarity&lt;/em&gt; is a better metric for search,
because it only measures the relative angle between vectors.&lt;/p&gt;
&lt;p&gt;The other main concept to consider is how to identify &lt;em&gt;nearest neighbours&lt;/em&gt;.
In other words,
given a search space populated with many vectors,
how do you find the closest ones according to your distance metric?&lt;/p&gt;
&lt;p&gt;Of course,
you can just iterate through the entire space
and collect nearest neighbours as you go.
This is the brute force approach
and works well for small datasets,
or if indexing latency is more important than search latency.
When there are many documents to search though,
it may take too long.&lt;/p&gt;
&lt;p&gt;Alternatively there&amp;rsquo;s a family of algorithms
collectively referred to as &lt;em&gt;approximate nearest neighbour&lt;/em&gt; (ANN).
Some of these involve constructing hash tables to optimise the process,
others divide the search space into subgraphs
and collate the results from traversing each one,
still others follow an iterative approach
of gradually partitioning the data until nearest neighbours are found,
and there are hybrid algorithms that combine elements of all three.
The cost of these ANN approaches is slower indexing
and the payoff is much faster search operations.
One of the fastest is called &lt;em&gt;hierarchical navigable small world&lt;/em&gt; (HNSW),
which is graph-based.&lt;/p&gt;
&lt;p&gt;The suggestion I made about picking a model
applies here too:
experiment with different algorithms
using data that is representative of real-world workloads for your application.
This stuff is easy to measure
and those measurements will be more informative
than anything a random blog post from the internet can tell you.&lt;/p&gt;
&lt;aside&gt;
  &lt;p class="smallprint jobspam"&gt;
    Discuss this post
    &lt;a href="https://www.reddit.com/r/programming/comments/1695kkq/vector_search_for_dummies/"&gt;on Reddit&lt;/a&gt;.
  &lt;/p&gt;
&lt;/aside&gt;</description><author>Phil Booth's Blog</author><pubDate>Sun, 03 Sep 2023 22:15:00 GMT</pubDate><guid isPermaLink="true">https://philbooth.me/blog/vector-search-for-dummies/</guid></item><item><title>Contextual Accessibility</title><link>https://devinprater.micro.blog/2023/09/03/contextual-accessibility.html</link><description>&lt;p&gt;I’m sure all this has been said many times before, but all I’ve been able to find so far is &lt;a href="https://hai.stanford.edu/news/creating-more-accessible-internet-context-matters"&gt;this article on Alt-Text&lt;/a&gt; which isn’t what I’m going for here. As a side note , I get that most members of society can only improve accessibility by adding Alt-text to the visual media they create, but isn’t it time we move a bit passed just image descriptions? Guides for Alt-text are everywhere. I am &lt;em&gt;not&lt;/em&gt; about to make yet another. This will, however, be a longer article than the usual, and I wouldn’t blame you for coming up for air during this one.&lt;/p&gt;
&lt;p&gt;In this article, I’ll talk about accessibility, for blind people, not just in regards to the screen reader, which also gets talked about, but also the operating system, and the activity the blind person is trying to do. In writing this article, I hope to zoom out a bit and show developers and others interested, that accessibility encompasses everything about the device, operating system, accessibility stack, screen reader, app, and person using the device. There is no solitary part of that stack.&lt;/p&gt;
&lt;h2 id="how-to-introduce-a-screen-reader"&gt;How to introduce a screen reader&lt;/h2&gt;
&lt;p&gt;I know this is going to be an edge case. But honestly, people in the accessibility space should be used to edge cases by now, and this happens much more than you’d think. We have our blind person here: a person who, up to now, has been fully sighted, but lost her sight instantly due to an accident. No, I’m not going into details because there are tons of ways a person can lose their vision. No, this isn’t a real person, and I’m not, in any way, saying that this is how instant sight loss occurs or effects people, but I know that if I lost my sight this way, this is probably what would happen. And I &lt;em&gt;know&lt;/em&gt; I’m not the only one that struggles with depression and anxiety.&lt;/p&gt;
&lt;p&gt;So this person, let’s name her Anna, is trying to get used to having an entire sense, just gone. Maybe she’s in pain, or maybe her mind is numb in trying to process everything. Or maybe she’s about to give up and find a sharp knife in which to end it all, searching and searching in the now permanent dark. But she remembers learning about Hellen Keller in school. Thinking back though, she didn’t remember covering any more than her childhood. Anna wants to find a book on the rest of Keller’s life, so stores that in the back of her mind, with a note to figure out how to get an audiobook.&lt;/p&gt;
&lt;p&gt;Now, you may be considering this amount of personal story to be a bit much, or over the top, or inappropriate for an informational post. This honestly makes blind people into nothing more than a test subject to be observed, and understood on a sort of acquaintance basis. I, for one, am tired of that. I, and every other blind person, am a living, thinking, feeling person. Notice that I didn’t say “blind user” above? No. A user is some case number or hypothetical list of assumptions to be grabbed, accounted for, and discarded at the end. But a person, well, they’re alive. They deal with their everyday trials and triumphs. They breathe and speak and change and grow and learn. A person is a bit less easy to discard, or to mock as “just another ‘luser’”. And yes, I’ve had to learn this too, over my years of teaching blind people&lt;/p&gt;
&lt;p&gt;Now, Anna got some help with her mental health, and she’s trying to learn to deal with being blind. She’s learned to feel for the light switch, to see if a light is on or not, since she can’t see them. She’s learned that she needs to keep everything in order, so that she can find things much easier. She’s even started making her own sandwiches, ham and cheese and some mayo, and pouring some cereal, even though she makes a bit of a mess with the milk. She now keeps a towel on the counter, for that reason.&lt;/p&gt;
&lt;p&gt;But today, she’s gonna do something that she thinks would be impossible. She feels around on her nightstand for the slab of glass. Her iPhone. She hasn’t touched it since she went blind a week ago. Or has it been a few weeks? A month? She takes a deep breath, and lets it out. She breathes for a moment, then starts to examine the device. A flat screen with two horizontal lines for the earpiece. Buttons on both sides. A flat piece of glass with the camera on the back. You get the idea.&lt;/p&gt;
&lt;p&gt;She then decides to try using Siri. Siri wasn’t especially useful to her before, but it’s all she has now. She tries some simple requests, like asking for the time, or the weather. She then decides to be a little more brave. Taking a deep breath, she asks “How can a blind person use an iPhone?” Siri responds, “Here’s what I found on the web. Check it out!”&lt;/p&gt;
&lt;p&gt;Anna waits a moment, remembering what this screen looks like. She waits, thinking that maybe Siri would be so kind as to read it out to her. Siri, it turns out, is not so kind. Her room is silent. Tears to not make a sound as they fall.&lt;/p&gt;
&lt;p&gt;She sits there for a moment, trying to pull herself together. Trying to gather up the pieces that had just went flying everywhere. She breathes in, and breathes out. She grips the phone in her hands, and tries the last thing she can think of. Because if this doesn’t work, then this expensive phone is nothing but expense to her. She tells Siri to call her mother. “Okay, calling Mom.” Her heart stops to listen. Her mind strains to hear. And the phone finally rings. And, after a moment, she hears her mother’s voice.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Now, this is without any kind of outside agency helping her, and I shudder to think of how many blind people go through this. They use Siri to text and call, read their texts and notifications, create a note and read it out, and set alarms. But apps, well, Youtube and books and anything besides texting, those are lost to them. Yes, having the ability to make a call or send a text is really important. And in this case, it’s probably what made the difference between barely surviving to possibly thriving. Because I’ve known people who come in with their iPhone and only know Siri. And maybe they’ve tried to use VoiceOver. But here’s where that falls apart.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Anna waited for the doctor to hang up the phone. She hated this part. The doctor hung up, but the automated system on which the call happened, well it takes a minute. Finally, she heard the static and click. She’d been noticing more of that kind of thing. How the tone of the milk changed as she filled a cup, or the way her foot falls echoed when she was near a door. She’d kind of started making sounds, clapping her hands or stepping a bit louder, just to experiment with the sounds. Not too much though. She never knew if someone was looking in through the window at that weird blind girl clapping at nothing or stomping her feet.&lt;/p&gt;
&lt;p&gt;Anna smiles at the memories, but reminds herself that she’s got a thing to try. Through some miracle, her mom had found an app on the iPhone, called VoiceOver. It was supposed to read out what is on the screen to you, and guide you through tasks on it. So, she told Siri to “Turn on VoiceOver.”&lt;/p&gt;
&lt;p&gt;Immediately, VoiceOver turned on. She heard a voice say “VoiceOver on.” And then it read the first app on the Home Screen. She was impressed, but wasn’t sure what to do next. The voice then said “double tap to open.” She tapped the screen twice. It read another app on the screen… Twice.&lt;/p&gt;
&lt;p&gt;She continued playing with it, but couldn’t make much sense of it. She could feel around the screen, but then what? When she tapped on an item to open it, it just read that item. She tapped twice like it said, but it just read the item again. In frustration, after hearing “double tap to open” for the hundredth time, she quickly jabbed at the phone. Miraculously, the app opened. She had been touching the weather app. She felt around the screen, hearing about, well, the weather. Exasperated, she put the phone down and took a nap. She deserved it.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;And here we have the meat of the issue. Yes, I tried that question to Siri, about how a blind person would use an iPhone, and she gave that “here’s what I found on the web” answer. This is even on iOS 17 beta, where Siri is supposed to be able to read webpages. But Siri can’t even ask the person if Siri should read the page or not. Because that’s just too hard.&lt;/p&gt;
&lt;p&gt;Siri could have easily been programmed to tell the person about VoiceOver, and at least give tips on how to use it. But no. If there’s a screen, people inherently can view the screen, and a digital talking assistant shouldn’t get in the sighted person’s way. Right?&lt;/p&gt;
&lt;p&gt;So, let’s look at accessibility here, in the context of a voice assistant. The voice assistant should never, ever assume that a person can see the screen. Like, ever. If it’s pulled up a long article, the assistant should offer to read the article. It doesn’t matter if a screen reader is on or not. As you read above, Anna didn’t even know the concept of a screen reader, let alone that one is on the iPhone.&lt;/p&gt;
&lt;p&gt;Mobile phones have an amazing accessibility potential specifically because of the voice assistant. This voice assistant is in popular culture already, so a newly blind person can at &lt;em&gt;lease&lt;/em&gt; use Siri, or Google Assistant, or even Bixby.&lt;/p&gt;
&lt;p&gt;So, an assistant should be able to help a blind person get started with their phone. The digital assistant can lead a blind person to a screen reader, or even read its documentation like a book. If Amazon’s Alexa can read books, then Apple’s Siri can handle reading some VoiceOver documentation.&lt;/p&gt;
&lt;p&gt;Moving forward, Anna had no idea how to use VoiceOver. Performing a double tap is incredibly difficult if you don’t have another person there who already knows, and can show you. You may think that it’s just tapping twice on the screen, but you have to do it very quickly compared to a normal tap. A little too slow, and it just thinks you’re trying to tap twice. And even when that’s done, the person has to learn how to swipe around apps, type on the keyboard, answer and end calls, and where the VoiceOver settings are to turn off the hints that are now getting in her way and making her lose focus on what she’s trying to do.&lt;/p&gt;
&lt;p&gt;It’s been more than a decade since VoiceOver was shipped. It’s seriously time for a tutorial. And yes, while I know that there are training centers for the blind across the US, I’m pretty sure there are plenty of blind people that never even hear about these centers, or are too embarrassed to go there, or can’t go due to health reasons.&lt;/p&gt;
&lt;p&gt;The screen reader, VoiceOver, in this case, was even &lt;em&gt;less&lt;/em&gt; helpful than the voice assistant. Imagine that. A voice assistant helped Anna make a phone call. VoiceOver, due to the lack of any kind of usage information, just got in her way. That’s really sad, and it’s something I see a lot. People just turn back to Siri because it’s easier, and it &lt;em&gt;works&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The idea that blind people just “figure it out” is nothing but the shirking of responsibility. Training centers should not have to cover the basics of a screen reader. But we do, and it takes anywhere from two weeks to a month or more of practice, keeping the student from slipping back into the sweet tones of Siri that can barely help them, and showing them all of what VoiceOver can do, which Apple just can’t manage to tell the person. Because it’s so much easier to show off a new feature than to build up the foundations just a tad bit more.&lt;/p&gt;
&lt;p&gt;Context matters. Yes, an advanced user won’t need to know all of VoiceOver’s features and how to do things on the phone. But someone who has just gone blind, or someone’s first iPhone, they need this. I don’t care if it’s Siri introducing the screen reader after the person asks how a blind person can use a phone, or if VoiceOver asks the user if they’d like to use it after a good minute or two of being on the home or Lock Screen with no activity, or if a tutorial pops up the first time a blind person uses VoiceOver. But something has got to change, because there are newly blind people every year. It isn’t like we people who were born blind are dying off and no blind people are replacing us, so that VoiceOver looks like this temporary measure until 50 years from now when there are no more blind people left, and VoiceOver can be left to rot and die.&lt;/p&gt;
&lt;p&gt;Disabilities, whether we like or not as a society, or even as a disability community, are going to be with us for a long time to come. You see how much money is poured into a big company’s accessibility team? Very little. That’s about how much money is also poured into medical fixes for disability. If it weren’t so, we’d all be seeing, and hearing, and walking, and being neurotypical, and so on, right now. They’ve had a good 40 years of modern medicine. They are not going to fix this in the next 40 years, either. Even if they do fix it for every disability, some people don’t want to typical. They find pride in their disability. That’s who they are.&lt;/p&gt;
&lt;h2 id="how-a-screen-reader-and-operating-system-work-together"&gt;How a screen reader and operating system work together&lt;/h2&gt;
&lt;p&gt;For the sake of my readers, the ones left at this point, and myself, I’m going to continue to use Anna for this section as well. Let’s say she has figured out the iPhone thanks to her mom reading articles to her, and now her time to upgrade her phone has arrived. But she’s not found a job yet, and an iPhone is way out of her current budget. Oh, did I tell you she was “gracefully let go” from her old job at Walmart? Yeah, there’s that too. Her bosses just &lt;em&gt;knew&lt;/em&gt; that she couldn’t possibly read the labels on the goods she normally organized and put on the shelves, because they don’t have Braille on them.&lt;/p&gt;
&lt;p&gt;Now, Anna is looking for a new phone. Her iPhone X is getting slow, and she’d finally paid off the phone with the help of her new SSDI bill, and her mom where needed. She doesn’t like to ask people to do things for her unless absolutely necessary.&lt;/p&gt;
&lt;p&gt;So, she goes to a phone store and picks out the best Android phone she can find. It’s not a flagship phone by any means. It’s not even a flagship killer. It’s just a Samsung phone. She pays for it, down a good $250, and walks out with the phone.&lt;/p&gt;
&lt;p&gt;Knowing how things generally are now, she holds down the power button, feels a nice little buzz (but not as nice as her iPhone could make), and asks her mom to read her some articles on how to use a Samsung with… whatever VoiceOver it has.&lt;/p&gt;
&lt;p&gt;So to make a long story short, she turns on TalkBack, goes through the tutorial built right into the screen reader, and is instantly in love. She can operate the screen reader, without her mom even needing to read her the commands and how to do them. She hits the finish button on the tutorial, and is ready to go.&lt;/p&gt;
&lt;p&gt;She first wants to make sure she’s running the latest version of everything. So, she opens the Galaxy Store, and feels around the screen. She finds a link of some kind, then a “don’t show again,” and a “close” button. Puzzled, she activates the close button. Now, she’s put into the actual store. She feels around the screen, finding tabs at the bottom. She’s not really sure what tabs are, but she’s seen them on some iPhone apps. She finds a “menu” tab, and activates that. Feeling around this new screen, she finds the updates item, and double taps on it. Nothing happens. She tries again, thinking she didn’t double tap hard enough, or fast enough, or slow enough. No matter what she tries, nothing happens.&lt;/p&gt;
&lt;p&gt;Frustrated and confused, Anna swipes to the left once, and finds a blank area. She decides to double tap it, thinking maybe that’s just how some things work on Android, like how sometimes on a website, a text label is right before a box where the information for that label goes. And she’s right. The updates screen opens, and she’s given a list of updatable apps.&lt;/p&gt;
&lt;p&gt;She then decides to look at the Play Store. She’s heard of that before, in ads on the TV and radio, where people are talking about downloading an app. She finds this app a bit easier to use, but also a lot more cluttered. She finds the search item easily, but cannot find the updates section. She decides to just give that a break for now.&lt;/p&gt;
&lt;p&gt;She decides to call her mom on her new phone, and see how that works. So, she double taps and holds on the home button to bring up Google Assistant. She knows how to do this because I don’t feel like making up some way she can figure that out. Oh and let’s say her contacts were magically backed up to Google and were restored to her new phone.&lt;/p&gt;
&lt;p&gt;TalkBack says “Google Assistant. Tap to dismiss assistant.” Startled, the command Anna was about to give is scattered across her mind. She finds and double taps on the back button. She tries again. The same thing happens. She’d never had that issue before. Trying to talk passed TalkBack, she talks over the voice saying “tap to dismiss assistant,” and says “call”… TalkBack then says “call”. TalkBack then says “double tap to finish.” Assistant then says “Here’s what I found.”&lt;/p&gt;
&lt;p&gt;Anna is done for the day. She puts the phone away, and from then on, just uses Samsung’s assistant that I don’t remember how to spell right now. Maybe after a year or two of this, she goes back to an iPhone. Let’s hope she finds out about a training center and gets a job somewhere, and lives happily ever after. Or something.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Having a screen reader work with an operating system requires tons of collaboration. The screen reader team, and indeed, every accessibility team that deals with an operating system, should be in the midst of operating system teams. From user interface elements to how a user is supposed to do things like update apps, or talk to an assistant, these things should be as easy as possible for a person using a screen reader. You, as a developer far removed from the user, don’t know if the person interacting with your software has just lost their vision, or is having a particularly suicidal day, or anything else going on in their lives. It’s not that far of a stretch to say that your software may be the difference between a person giving up, or deciding that maybe being blind isn’t a life-ending event after all.&lt;/p&gt;
&lt;p&gt;Let’s go through what happened here. TalkBack, in this case Samsung’s TalkBack that’s still running 13 even though 14 has been out for months now, does have a built-in tutorial. It’s been there for as long as I’ve used it. And that’s extremely commendable for the TalkBack team. Also, they keep updating it.&lt;/p&gt;
&lt;p&gt;Now, let’s focus on the Galaxy Store for a moment. An ad wasn’t a big deal in this case. It didn’t jumble up and slow down the screen reader, like some other ads tend to do. It was just a simple link. But in a lot of cases, these ads aren’t readable as ads. They’re just odd things that show up, with buttons to not show the ad again, and to close the ad. It’d be kinda nice if the window read “ad” or something before the ad content.&lt;/p&gt;
&lt;p&gt;Now, for the worst thing about the store. The download and update buttons are broken now, with no fix coming any time soon. Operating system developers, even if they’re developing from a base like Android, need accessibility teams including a blind person, Deaf person, and so on. And those disabled people need to have enough power to stop an update that breaks a part of the experience, especially one as important as updating apps.&lt;/p&gt;
&lt;p&gt;A screen reader can only read what an app tells it to read, unless there’s AI involved. And for me, there was. When TalkBack landed on that unlabeled item, it said something like “downloads” so I knew to click on that instead of the item label that’s just sitting there outside the actual item. A screen reader is only part of a wider system. A app has to be labeled correctly. An operating system has to be able to tell the screen reader about the item label. And the person has to know how to use the screen reader, and operating system, and app.&lt;/p&gt;
&lt;p&gt;And finally, the Google Assistant. Context matters a lot here. What is the user trying to do? Talk to a digital assistant. Would you like it if the person you are talking to suddenly talks over you, and then begins repeating the things you’re saying? No? I thought not. In these kinds of contexts, a screen reader should be silent. The digital assistant, if needed, should be reading any options that appear, like choices for a restaurant or phone number. There should be no doubt here. There are times when a screen reader should be silent, and this is one of the most important. If you are wondering about this, ask a blind person. We &lt;em&gt;do&lt;/em&gt; exist, and we’ve had enough of these stupid issues that could be resolved if we are just asked.&lt;/p&gt;
&lt;h1 id="conclusion"&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Alt-text is just the tip of the iceberg of accessibility. After all, if a person isn’t told how to use the screen reader, what good is Alt-text if they can’t even get to the site to read it? A screen reader is not enough. An app has to be built to be accessible and easy to use, or a person won’t know where to go to do the thing they want. An operating system has to have a good enough accessibility framework to allow a screen reader to, say, turn off its own touch interaction methods to allow a person to play a video game, or play a piano. All of these pieces create the accessibility experience that a person has. And this isn’t even getting into Braille and tactile interfaces.&lt;/p&gt;
&lt;p&gt;I hope this post has shown some of what we as blind people experience. After a decade of development, you’d think some of these things, issues, and capability for issues, would be resolved a long time ago. But developers still don’t understand that they’re not developing alone, and that it takes constant cooperation to make things not just accessible as in every button is labeled, but great as in the user is confident in how to use their screen reader, the app they’re in, and the operating system as a whole. Until then, these problems will &lt;em&gt;not&lt;/em&gt; be resolved. A company-wide, cultural mindset does not fix itself. Blind people are so used to this that very few even know that they &lt;em&gt;can&lt;/em&gt; contact Apple or Google developers, and fewer still think it’ll do any good. They see developers as ultra-smart people in some castle somewhere, way out of mind of the people they develop for, and there’s a reason for that. Developers are going to have to speak with the people that rely on what they build. And not just the people that will tell the developers what they want to hear, either. Developers need to be humble, and never, ever think that what they’re building is anywhere close to good enough, because once they do, they start to dismiss accounts of things not working right, or a need for something better, or different, or more choices. Until these things happen, until people can trust developers to actually be on their side, these kinds of stories will continue, unnoticed and undiscussed.&lt;/p&gt;</description><author>Devin Prater's blog</author><pubDate>Sun, 03 Sep 2023 15:41:43 GMT</pubDate><guid isPermaLink="true">https://devinprater.micro.blog/2023/09/03/contextual-accessibility.html</guid></item><item><title>Yosemite August 2023</title><link>https://solomon.io/yosemite-august-2023/</link><description>I have been to Yosemite a couple dozen times over the years. The park has many spectacular sights, spots and trails.</description><author>Sam Solomon</author><pubDate>Sun, 03 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://solomon.io/yosemite-august-2023/</guid></item><item><title>Style is consistent constraint</title><link>https://stephango.com/style</link><description>&lt;p&gt;Oscar Wilde once said:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Consistency is the last refuge of the unimaginative.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When it comes to ideas, I agree — allow your mind to be changed. When it comes to process, I disagree. Style emerges from consistency, and having a style opens your imagination. Your mind should be flexible, but your process should be repeatable.&lt;/p&gt;

&lt;p&gt;Style is a set of constraints that you stick to.&lt;/p&gt;

&lt;p&gt;You can explore many types of constraints: colors, shapes, materials, textures, fonts, language, clothing, decor, beliefs, flavors, sounds, scents, rituals. Your style doesn’t have to please anyone else. Play by your own rules. Everything you do is open to stylistic interpretation.&lt;/p&gt;

&lt;p&gt;A style can be a system, a pattern, a set of personal guidelines. Here are a few of mine:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;I wear monochromatic clothing without logos&lt;/li&gt;
  &lt;li&gt;I use &lt;code class="language-plaintext highlighter-rouge"&gt;YYYY-MM-DD&lt;/code&gt; dates everywhere&lt;/li&gt;
  &lt;li&gt;I pluralize tag and folder names (e.g. &lt;code class="language-plaintext highlighter-rouge"&gt;#people&lt;/code&gt; not &lt;code class="language-plaintext highlighter-rouge"&gt;#person&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;I use &lt;a class="internal-link" href="/file-over-app"&gt;plain text files&lt;/a&gt; for all my writing&lt;/li&gt;
  &lt;li&gt;I ask myself &lt;a class="internal-link" href="/40-questions"&gt;40 questions every year&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;I meal prep lunches every week, shave my head twice a week&lt;/li&gt;
  &lt;li&gt;I write &lt;a class="internal-link" href="/concise"&gt;concise essays&lt;/a&gt;, less than 500 words&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Collect constraints you enjoy. Unusual constraints make things more fun. You can always change them later. This is &lt;em&gt;your&lt;/em&gt; style, after all. It’s not a life commitment, it’s just the way you do things. For now.&lt;/p&gt;

&lt;p&gt;Having a style collapses hundreds of future decisions into one, and gives you focus. I always pluralize tags so I never have to wonder what to name new tags.&lt;/p&gt;

&lt;p&gt;Style gives you leverage. Every time you reuse your style you save time. A durable style is a great investment.&lt;/p&gt;

&lt;p&gt;Style helps you know when you’re breaking your constraints. Sometimes you have to. And if you want to edit your constraints, you can. It will be easier to adopt the new constraints if you already had some clearly defined.&lt;/p&gt;

&lt;p&gt;You don’t need a style for everything. Make a deliberate choice about what needs consistency and what doesn’t.&lt;/p&gt;

&lt;p&gt;If you stick with your constraints long enough, your style becomes a cohesive and recognizable &lt;a class="internal-link" href="/in-good-hands"&gt;point of view&lt;/a&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id="appendix"&gt;Appendix&lt;/h2&gt;

&lt;p&gt;I am starting a collection of interesting personal style choices. Please &lt;a class="internal-link" href="/about"&gt;send me examples&lt;/a&gt; and I’ll add them to the list.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://www.youtube.com/@ScottYuJan"&gt;Scott Yu-Jan&lt;/a&gt; &lt;a href="https://www.youtube.com/watch?v=3GBPYRG9jM0"&gt;paints all his tools white&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.youtube.com/@2ynthetic"&gt;2ynthetic&lt;/a&gt; uses a &lt;a href="https://www.youtube.com/watch?v=4xMo2PsLi3c"&gt;limited palette for outfits&lt;/a&gt; and &lt;a href="https://www.youtube.com/watch?v=1RqBrl0-qOA"&gt;office decor&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://johnnydecimal.com/"&gt;Johnny Decimal&lt;/a&gt; is a system to organize digital data.&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.youtube.com/@casey"&gt;Casey Neistat&lt;/a&gt; labels everything &lt;a href="https://www.youtube.com/watch?v=vb60rrtTddQ"&gt;in his studio&lt;/a&gt; with paint markers.&lt;/li&gt;
  &lt;li&gt;Ryan Hoover &lt;a href="https://www.ryanhoover.me/post/why-i-never-change-my-profile-pic"&gt;never changes his profile picture&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Wes Anderson uses &lt;a href="https://www.marksimonson.com/notebook/view/RoyalTenenbaumsWorldofFutura"&gt;the typeface Futura&lt;/a&gt; in many of his films.&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Steve_Jobs"&gt;Steve Jobs&lt;/a&gt; wore the same Issey Miyake black turtleneck and New Balance 991 shoes every day.&lt;/li&gt;
  &lt;li&gt;George R.R. Martin writes his novels on the 40 year old DOS operating system&lt;/li&gt;
  &lt;li&gt;Beethoven always counted exactly 60 coffee beans to make coffee, especially for visitors.&lt;/li&gt;
  &lt;li&gt;Jesper Kouthoofd of &lt;a href="https://teenage.engineering/"&gt;Teenage Engineering&lt;/a&gt; &lt;a href="https://scandinavianmind.com/feature/human-touch-interview-jesper-kouthoofd-teenage-engineering"&gt;only uses lowercase&lt;/a&gt; because uppercase communicates too much authority&lt;/li&gt;
  &lt;li&gt;Osvaldo Cavandoli created the cartoon &lt;a href="https://en.wikipedia.org/wiki/La_Linea_(TV_series)"&gt;La Linea&lt;/a&gt; using an animated white line, colored backgrounds, and unintelligible vocalizations&lt;/li&gt;
&lt;/ul&gt;</description><author>Steph Ango</author><pubDate>Sun, 03 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://stephango.com/style</guid></item><item><title>How Discord and AI will shape Web 4.0</title><link>https://omarabid.com/future-of-web/</link><description>Exploring how Web 4.0 will look like based on current trends.</description><author>Omar Abid - Personal Blog</author><pubDate>Sun, 03 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://omarabid.com/future-of-web/</guid></item><item><title>Framework 13: Things that Surprised Me</title><link>https://cmart.blog/framework-surprise/</link><description>&lt;p&gt;If you don&amp;rsquo;t know what a &lt;a href="https://frame.work"&gt;Framework laptop&lt;/a&gt; is, go read &lt;a href="https://www.theverge.com/23725039/framework-laptop-13-2023-intel-review"&gt;someone&lt;/a&gt; &lt;a href="https://www.tomshardware.com/reviews/framework-laptop-13-intel-2023"&gt;else&amp;rsquo;s&lt;/a&gt; &lt;a href="https://www.ifixit.com/News/51614/framework-laptop-teardown-10-10-but-is-it-perfect"&gt;review&lt;/a&gt; first. This ain&amp;rsquo;t the best introduction, but it &lt;em&gt;will&lt;/em&gt; be helpful if you are nitpicky, migrating from a Thinkpad, and/or running Linux. (I also posted this on the &lt;a href="https://community.frame.work/t/framework-13-things-that-surprised-me/36235"&gt;Framework Discourse&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;I am (approximately) a software and cloud infrastructure engineer. For about 8 years I&amp;rsquo;ve used Linux on Thinkpads, mostly &lt;a href="https://en.m.wikipedia.org/wiki/Debian"&gt;Debian&lt;/a&gt; with the default &lt;a href="https://en.m.wikipedia.org/wiki/GNOME"&gt;GNOME&lt;/a&gt; desktop environment. For the past three years I&amp;rsquo;ve also used the wonderful &lt;a href="https://github.com/paperwm/PaperWM"&gt;PaperWM&lt;/a&gt; GNOME extension.  I&amp;rsquo;ve had my new Framework 13 (13th gen Intel CPU) for a couple of weeks. It&amp;rsquo;s replacing a Thinkpad T480, which I previously wrote about &lt;a href="https://old.reddit.com/r/thinkpad/comments/jreqej/new_t480_impressions_upgrading_from_t450s"&gt;here&lt;/a&gt;. (Before that I had a T450s, and before that I had the &lt;em&gt;very first&lt;/em&gt; unibody Macbook.)&lt;/p&gt;</description><author>cmart's blog</author><pubDate>Sun, 03 Sep 2023 01:18:36 GMT</pubDate><guid isPermaLink="true">https://cmart.blog/framework-surprise/</guid></item><item><title>Non-English speech synthesis</title><link>https://dorotac.eu/posts/speech/</link><description>&lt;h1&gt;Non-English speech synthesis&lt;/h1&gt;
&lt;p&gt;If you're like me, you've been on the English-language Internet for so long that English is how you think of computer things.&lt;/p&gt;
&lt;p&gt;Sadly, the English Internet has blind spots. The most obvious one of those is that the Eglish Internet doesn't care about other languages. So if you go and ask the search engine about "Linux speech synthesizers", hoping to get a robot voice to say "&lt;a href="https://jazda.org"&gt;jazda&lt;/a&gt;", you'll realize that the results have nothing to offer you.&lt;/p&gt;
&lt;p&gt;Instead, you stop for a moment, and decide to go to the smaller, a bit backwater, non-English Internet, and you ask "syntezator mowy linux", knowing that search results are likely to be infested with non-free solutions which you don't want to see, and you might not even find a free one.&lt;/p&gt;
&lt;p&gt;The results are just as bad as in the English search, but somehow still worse. There's the unmaintained, one-person effort, a tutorial for Festival which asks you to download the voice bank from a long-dead URL, and a bunch of links referring to espeak which &lt;a href="jazda_espeak.wav"&gt;sounds awful even for a robot voice&lt;/a&gt; when asked to go &lt;a href="https://github.com/espeak-ng/espeak-ng/issues/539"&gt;outside of its English comfort zone&lt;/a&gt;. The guides to installing an Android software aren't even worth mentioning.&lt;/p&gt;
&lt;p&gt;But there's something sparkling in the mud. A &lt;a href="https://bon.uw.edu.pl/magda-i-natan-dwa-polskie-glosy-dla-syntezatora-mowy-rhvoice/"&gt;university website&lt;/a&gt; links to &lt;a href="https://rhvoice.org/"&gt;RHVoice&lt;/a&gt;, which is even &lt;a href="https://packages.debian.org/search?keywords=rhvoice&amp;amp;searchon=names&amp;amp;suite=stable&amp;amp;section=all"&gt;packaged for Debian&lt;/a&gt; – although I don't know why it's in the non-free repo, the license on the github page is listed as GPL. If it sounds like an easy solution, it almost is. Almost.&lt;/p&gt;
&lt;h2&gt;RHVoice&lt;/h2&gt;
&lt;p&gt;I don't run Debian, so I install the software in a container.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;podman run -ai debian:bookworm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once inside, I install vim, and I install the package and some voices:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;echo 'deb http://deb.debian.org/debian bookworm non-free" &amp;gt;&amp;gt; /etc/apt/sources.list
apt update
apt install rhvoice rhvoice-polish
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, I tell it to say something:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;echo "jazda" | RHVoice-test -p natan -o - &amp;gt; test.wav
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will place the recording in a file. On the host side, I retrieve it using the container ID:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;podman cp 1ea7197c68f6:test.wav Documents/jazda.wav
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's it! The recording &lt;a href="jazda.wav"&gt;sounds okay&lt;/a&gt;, and I can put it alongside the IPA pronunciation on the &lt;a href="https://jazda.org"&gt;web site&lt;/a&gt;.&lt;/p&gt;</description><author>dcz's posts</author><pubDate>Sat, 02 Sep 2023 17:00:00 GMT</pubDate><guid isPermaLink="true">https://dorotac.eu/posts/speech/</guid></item><item><title>The Mystery of the Bloomfield Bridge</title><link>http://evantravers.com/articles/2023/09/02/the-mystery-of-the-bloomfield-bridge/</link><description>&lt;em&gt;
  &lt;svg height="16" viewBox="0 0 24 24" width="16" xmlns="http://www.w3.org/2000/svg"&gt;
    # &lt;path d="M 4 4.44 v 2.83 c 7.03 0 12.73 5.7 12.73 12.73 h 2.83 c 0 -8.59 -6.97 -15.56 -15.56 -15.56 Z m 0 5.66 v 2.83 c 3.9 0 7.07 3.17 7.07 7.07 h 2.83 c 0 -5.47 -4.43 -9.9 -9.9 -9.9 Z M 6.18 15.64 A 2.18 2.18 0 0 1 6.18 20 A 2.18 2.18 0 0 1 6.18 15.64" fill="currentColor"&gt;&lt;/path&gt;
  &lt;/svg&gt;
  This is an RSS-only post. It's a secret! Read more about &lt;a href="/rss-club"&gt;RSS Club&lt;/a&gt;.
&lt;/em&gt;
&lt;p&gt;I have never been so fascinated by a bridge in my life. Tyler's enthusiasm for local history is infectious and delightful. It's inspiring and super fun… the lengths to which he goes!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;While I am dedicated to this search, I am not about to fly down to Kansas City to dig through federal archives, especially when those documents may or may not be there...&lt;/p&gt;
&lt;p&gt;...just kidding. Of course I flew down to Kansas City to dig through the federal archives!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I miss the old internet that was mostly wonderful humans answering questions they posed themselves.&lt;/p&gt;
&lt;p&gt;There's also a good warning to ask an expert early in your project… they have hard-earned heuristics:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;She told me to stop looking at old documents: &amp;quot;no one writes down the real reason for infrastructure projects.&amp;quot; She said I needed to look for &lt;em&gt;people in power&lt;/em&gt;. Specifically, if I want to prove my theory, I should look for people who would be connected to both the church and the bridge.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you like ridiculous researched rants and history (with a touch of UX desire paths!) then give this a read.&lt;/p&gt;&lt;p&gt;&lt;a class="u-in-reply-to  is-reply" href="https://tylervigen.com/the-mystery-of-the-bloomfield-bridge"&gt;The Mystery of the Bloomfield Bridge - tylervigen.com&lt;/a&gt;&lt;/p&gt;</description><author>trv.rs</author><pubDate>Sat, 02 Sep 2023 13:17:00 GMT</pubDate><guid isPermaLink="true">http://evantravers.com/articles/2023/09/02/the-mystery-of-the-bloomfield-bridge/</guid></item><item><title>CLI tip 21: inplace file editing with GNU awk</title><link>https://learnbyexample.github.io/tips/cli-tip-21/</link><description>&lt;p&gt;You can use the &lt;code&gt;-i&lt;/code&gt; option with &lt;code&gt;GNU awk&lt;/code&gt; to load libraries. The &lt;code&gt;inplace&lt;/code&gt; library comes by default with the &lt;code&gt;GNU awk&lt;/code&gt; installation. Thus, you can use &lt;code&gt;-i inplace&lt;/code&gt; to modify the original input itself. Make sure to test that the code is working as intended before using this option.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ cat table.txt
&lt;/span&gt;&lt;span&gt;brown bread mat cake &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;42
&lt;/span&gt;&lt;span&gt;blue cake mug shirt &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;7
&lt;/span&gt;&lt;span&gt;yellow banana window shoes &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3.14
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# retain only the first and third fields
&lt;/span&gt;&lt;span&gt;$ awk &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;i inplace &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{print $1, $3}'&lt;/span&gt;&lt;span&gt; table.txt
&lt;/span&gt;&lt;span&gt;$ cat table.txt
&lt;/span&gt;&lt;span&gt;brown mat
&lt;/span&gt;&lt;span&gt;blue mug
&lt;/span&gt;&lt;span&gt;yellow window
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can provide a backup extension by setting the &lt;code&gt;inplace::suffix&lt;/code&gt; special variable. For example, if the input file is &lt;code&gt;ip.txt&lt;/code&gt; and &lt;code&gt;inplace::suffix='.orig'&lt;/code&gt; is used, the backup file will be named as &lt;code&gt;ip.txt.orig&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ cat marks.txt
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Name    Physics  Maths
&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Moe  &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;76  82
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Raj  &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;56  64
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ awk &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;i inplace &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;v inplace::suffix=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'.bkp' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;v &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;OFS&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{$1=$1} 1'&lt;/span&gt;&lt;span&gt; marks.txt
&lt;/span&gt;&lt;span&gt;$ cat marks.txt
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Name&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Physics&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Maths
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Moe&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;76&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;82
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Raj&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;56&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;64
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# original file is preserved in 'marks.txt.bkp'
&lt;/span&gt;&lt;span&gt;$ cat marks.txt.bkp
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Name    Physics  Maths
&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Moe  &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;76  82
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Raj  &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;56  64
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; Earlier versions of &lt;code&gt;GNU awk&lt;/code&gt; used &lt;code&gt;INPLACE_SUFFIX&lt;/code&gt; variable instead of &lt;code&gt;inplace::suffix&lt;/code&gt;. Also, you can use &lt;code&gt;inplace::enable&lt;/code&gt; variable to dynamically control whether files should be inplaced or not. See &lt;a href="https://www.gnu.org/software/gawk/manual/gawk.html#Extension-Sample-Inplace"&gt;gawk manual: Enabling In-Place File Editing&lt;/a&gt; for more details.&lt;/p&gt;
&lt;p&gt;&lt;img alt="warning" src="/images/warning.svg" /&gt; See &lt;a href="https://unix.stackexchange.com/q/749645/109046"&gt;this unix.stackexchange thread&lt;/a&gt; for details about security implications of using the &lt;code&gt;-i&lt;/code&gt; option and workarounds.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See my &lt;a href="https://github.com/learnbyexample/learn_gnuawk"&gt;CLI text processing with GNU awk&lt;/a&gt; ebook if you are interested in learning about the &lt;code&gt;GNU awk&lt;/code&gt; command in more detail.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Sat, 02 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/cli-tip-21/</guid></item><item><title>View Transitions in Oracle APEX</title><link>https://hartenfeller.dev/blog/apex-view-transitions</link><description>View Transitions allow you to animate page transitions. This makes the UI more polished and engaging.</description><author>Philipp Hartenfeller Blog RSS Feed</author><pubDate>Sat, 02 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://hartenfeller.dev/blog/apex-view-transitions</guid></item><item><title>2023–09–02: Linux repository changes</title><link>https://xnux.eu/log/#094</link><author>megi's PinePhone Development Log</author><pubDate>Sat, 02 Sep 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#094</guid></item><item><title>The Photo Game: Texture</title><link>https://andrewbridges.org/the-photo-game-texture/</link><description>I wanted to include a picture of a tree trunk as soon as I read this week's prompt. It's the perfect subject for a contrasty black and white photo.</description><author>Show Notes</author><pubDate>Fri, 01 Sep 2023 17:04:14 GMT</pubDate><guid isPermaLink="true">https://andrewbridges.org/the-photo-game-texture/</guid></item><item><title>List of free AWS Knowledge Badges</title><link>https://tobilg.com/posts/list-of-free-aws-knowledge-badges/</link><description>As the Skillbuilder website is sometimes a bit hard to navigate, here's the full list of free badges you can do on AWS Skillbuilder: AWS Knowledge: Cloud Essentials AWS Knowledg...</description><author>tobilg.com</author><pubDate>Fri, 01 Sep 2023 13:32:10 GMT</pubDate><guid isPermaLink="true">https://tobilg.com/posts/list-of-free-aws-knowledge-badges/</guid></item><item><title>A Bit of SaaS Weekly: End of Summer</title><link>https://ethanmick.com/a-bit-of-saas-weekly-end-of-summer/</link><description>&lt;p&gt;This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by &lt;a href="https://ethanmick.com/"&gt;Ethan Mick&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;It&amp;apos;s Labor Day weekend in the United States, the unofficial end of summer. I hope you all had a fantastic summer and are looking forward to the end&lt;/p&gt;</description><author>Build SaaS with Ethan</author><pubDate>Fri, 01 Sep 2023 13:00:39 GMT</pubDate><guid isPermaLink="true">https://ethanmick.com/a-bit-of-saas-weekly-end-of-summer/</guid></item><item><title>The Pain and Poetry of Python</title><link>https://zackproser.com/blog/pain-poetry-python</link><description>A history of package management and virtualenvs in Python and why we migrated the Pinecone Python client to Poetry</description><author>Zachary Proser</author><pubDate>Fri, 01 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/pain-poetry-python</guid></item><item><title>Finding out your public IP address via curl</title><link>https://heitorpb.github.io/bla/ip-rs/</link><description>How to find out your public IP address on the command line after reinventing the wheel using Rust, Axum, Nix, a docker image and Fly.io.</description><author>Heitor's log</author><pubDate>Fri, 01 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://heitorpb.github.io/bla/ip-rs/</guid></item><item><title>Downloading images from IST</title><link>https://studiofreya.org/posts/downloading-images-from-ist/</link><description>&lt;p&gt;There is a service called &amp;ldquo;IST Skola&amp;rdquo; which our daycare is using.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s an app for taking attendence, delivery and pickups and it is possible for the staff to share images and text from the preceeding days / weeks.&lt;/p&gt;
&lt;p&gt;A couple of years ago they used another service called Truegroups. With that service it was possible to download the images and reports. We also got emailed copies of the images and reports.&lt;/p&gt;
&lt;p&gt;When they changed to IST, it was not possible to download images, copy the reports or get an copy on email. The only &amp;ldquo;simple&amp;rdquo; way of making a copy of the images was by taking screenshots&amp;hellip;&lt;/p&gt;</description><author>Studiofreya SSG Site</author><pubDate>Fri, 01 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://studiofreya.org/posts/downloading-images-from-ist/</guid></item><item><title>Becoming a contractor</title><link>https://ochagavia.nl/blog/becoming-a-contractor/</link><description>How many roads must a programmer walk down before you call them a contractor? It&amp;rsquo;s been about three years since I went down this (bumpy) path, and I finally have the feeling that my contracting business is &amp;ldquo;bootstrapped&amp;rdquo;1. Here&amp;rsquo;s an account of my journey so far, hoping you find it inspiring!
0. Contents Articles about contracting and freelancing usually take the form of abstract advice: how to find clients, how much you should charge, etc.</description><author>Consulting on Adolfo Ochagavía</author><pubDate>Fri, 01 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ochagavia.nl/blog/becoming-a-contractor/</guid></item><item><title>Build dynamic navs with current_page? (and conditional classes)</title><link>https://railsnotes.xyz/blog/rails-dynamic-nav-current-page</link><description>This article explores a dynamic nav component I built using the current_page? helper method, plus Rails' conditional class helpers. The result? A simple, dynamic navbar component, with different styling based on current page. Plus I threw in the ViewComponent version too 😉</description><author>RailsNotes, the Ruby on Rails guides you wished you had.</author><pubDate>Fri, 01 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://railsnotes.xyz/blog/rails-dynamic-nav-current-page</guid></item><item><title>Husqvarna 701 Supermoto</title><link>https://ellie.wtf/motorcycles/701/</link><description>&lt;p&gt;I've had my 701 supermoto since June 2022, and absolutely love it. In &lt;a href="https://ellie.wtf/life/2022/"&gt;2022&lt;/a&gt; I rode it for 2000 miles around Europe!&lt;/p&gt;
&lt;h1 id="remap"&gt;Remap&lt;/h1&gt;
&lt;p&gt;The bike runs super lean and hot from the factory. Decat + new headers + can will result in the bike being dangerously lean, and a remap is recommended. Besides, you won't be making the most of the new parts without it!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Power commander 6 required if you go the PCM route. It requires a fueling plug &lt;em&gt;in addition&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Some companies can remap the ECU directly&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.coober.eu/product/hqv-701-series-lm-ecu/" rel="external"&gt;Coober ECU&lt;/a&gt; is an option&lt;/li&gt;
&lt;li&gt;Older models can take a fueling plug, but I don't believe this is an option in the 2022&lt;/li&gt;
&lt;li&gt;I had mine remapped by &lt;a href="https://www.cjsracing.co.uk/" rel="external"&gt;CJS Racing&lt;/a&gt; near Bristol, super happy with the result&lt;/li&gt;
&lt;/ul&gt;</description><author>Ellie's Notes</author><pubDate>Fri, 01 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ellie.wtf/motorcycles/701/</guid></item><item><title>Automatically load zfs keys at boot</title><link>https://ellie.wtf/notes/auto-load-zfs-keys-at-boot/</link><description>&lt;p&gt;I recently setup a new encrypted dataset. Ubuntu/openzfs came with a systemd service to handle mounting it at boot, but not loading the keys - therefore, on reboot, it would fail to mount&lt;/p&gt;
&lt;p&gt;I did some googling and found this: https://github.com/openzfs/zfs/issues/8750#issuecomment-497500144&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;cat&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; &amp;lt;&amp;lt;&lt;/span&gt;&lt;span style="color: #A89984;"&gt; 'EOF'&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; &amp;gt;&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; /etc/systemd/system/zfs-load-key@.service&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #B8BB26;"&gt;[Unit]&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #B8BB26;"&gt;Description=Load ZFS keys&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #B8BB26;"&gt;DefaultDependencies=no&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #B8BB26;"&gt;Before=zfs-mount.service&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #B8BB26;"&gt;After=zfs-import.target&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #B8BB26;"&gt;Requires=zfs-import.target&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #B8BB26;"&gt;[Service]&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #B8BB26;"&gt;Type=oneshot&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #B8BB26;"&gt;RemainAfterExit=yes&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #B8BB26;"&gt;ExecStart=/sbin/zfs load-key %I&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #B8BB26;"&gt;[Install]&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #B8BB26;"&gt;WantedBy=zfs-mount.service&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #A89984;"&gt;EOF&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use like so:&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;systemctl&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; enable zfs-load-key@tank-enc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to, for example, enable key loading for the volume &lt;code&gt;tank/enc&lt;/code&gt;. It will automatically replace / with -&lt;/p&gt;</description><author>Ellie's Notes</author><pubDate>Fri, 01 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ellie.wtf/notes/auto-load-zfs-keys-at-boot/</guid></item><item><title>Git snippets</title><link>https://ellie.wtf/notes/git-snippets/</link><description>&lt;h2 id="remove-untracked-files"&gt;Remove untracked files&lt;/h2&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;git clean -fdx&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-f&lt;/code&gt; force&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-d&lt;/code&gt; include directories&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-x&lt;/code&gt; also remove ignored files&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="disable-push-to-master-main"&gt;Disable push to master/main&lt;/h2&gt;
&lt;p&gt;It's pretty easy to accidentally push to main/master (if for some reason you cannot enable branch protection), so putting&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;[branch.main]&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;	pushRemote =&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;[branch.master]&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;	pushRemote =&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;in your git config sorts this out! If you would like to intentionally push to an important branch, &lt;code&gt;git push origin main&lt;/code&gt; will sort you out.&lt;/p&gt;</description><author>Ellie's Notes</author><pubDate>Fri, 01 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ellie.wtf/notes/git-snippets/</guid></item><item><title>Helmfile</title><link>https://ellie.wtf/notes/helmfile/</link><description>&lt;h2 id="helmfile"&gt;Helmfile&lt;/h2&gt;
&lt;p&gt;Helm is totally adequate (I'm uncomfortable calling it good) for deploying an
app, and potentially a small number of dependencies. It can quickly spiral out
of control when there are many services that need deploying, with varying
levels of dependency.&lt;/p&gt;
&lt;p&gt;Helmfile is great for describing a large set of charts to deploy, and their
values. It's nice to be able to set values per environment, and to be able to
deploy charts from a variety of sources - git repos, official repos, and the
local filesystem.&lt;/p&gt;
&lt;h3 id="write-values"&gt;write-values&lt;/h3&gt;
&lt;p&gt;Ensuring you get the correct YAML and Go template encantation can be annoying,
and testing it out by deploying is not ideal. In lieu of proper testing,
&lt;code&gt;helmfile write-values&lt;/code&gt; is great as it runs all the templating and dumps files
on disk for inspection&lt;/p&gt;</description><author>Ellie's Notes</author><pubDate>Fri, 01 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ellie.wtf/notes/helmfile/</guid></item><item><title>jq snippets</title><link>https://ellie.wtf/notes/jq-snippets/</link><description>&lt;h1 id="jq"&gt;jq&lt;/h1&gt;
&lt;p&gt;Just a collection of jq things I've found useful&lt;/p&gt;
&lt;h2 id="arrays"&gt;Arrays&lt;/h2&gt;
&lt;h3 id="get-an-array-element-by-index"&gt;Get an array element by index&lt;/h3&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;echo '[1, 2, 3, 4, 5]' | jq '.[0]' # =&amp;gt; 1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="get-the-length-of-an-array"&gt;Get the length of an array&lt;/h3&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;echo '[1, 2, 3, 4, 5]' | jq 'length' # =&amp;gt; 5&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</description><author>Ellie's Notes</author><pubDate>Fri, 01 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ellie.wtf/notes/jq-snippets/</guid></item><item><title>Kube State Metrics</title><link>https://ellie.wtf/notes/kube-state-metrics/</link><description>&lt;p&gt;Kube state metrics exposes kubernetes metrics in a way that they can be scraped by prometheus. It uses the kubernetes API to create a snapshot of the state of your cluster, and then exposes that on &lt;code&gt;/metrics&lt;/code&gt;, ready to be ingested by your monitoring.&lt;/p&gt;</description><author>Ellie's Notes</author><pubDate>Fri, 01 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ellie.wtf/notes/kube-state-metrics/</guid></item><item><title>Linux</title><link>https://ellie.wtf/notes/linux/</link><description>&lt;p&gt;Just a reference page for all the linux things I forget&lt;/p&gt;
&lt;h3 id="filesystem-types"&gt;Filesystem types&lt;/h3&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;fd00 - Linux RAID&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="creating-software-raid-device"&gt;Creating software RAID device&lt;/h3&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;mdadm --create --verbose /dev/md&amp;lt;NUMBER&amp;gt; --level=&amp;lt;RAID LEVEL&amp;gt; --raid-devices=&amp;lt;DEVICES&amp;gt; /dev/one /dev/two ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="get-device-uuid"&gt;Get device UUID&lt;/h3&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;blkid&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="networking"&gt;Networking&lt;/h2&gt;
&lt;h3 id="interfaces"&gt;Interfaces&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Interface names must be &amp;lt;16 chars&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="create-bridge"&gt;Create bridge&lt;/h3&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;ip link add br0 type bridge&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="add-ip-to-bridge"&gt;Add IP to bridge&lt;/h3&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;ip addr add 172.16.0.1/16 dev br0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</description><author>Ellie's Notes</author><pubDate>Fri, 01 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ellie.wtf/notes/linux/</guid></item><item><title>PostgreSQL</title><link>https://ellie.wtf/notes/postgresql/</link><description>&lt;p&gt;Some postgres snippets! Just a reference page for the things I forget a lot.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pg_ctl init -D path&lt;/code&gt; - init new database + config at &lt;code&gt;path&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pg_hba.conf&lt;/code&gt; - configure host based auth&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pg_ident.conf&lt;/code&gt; - map system users to database users&lt;/li&gt;
&lt;li&gt;&lt;code&gt;postgresql.conf&lt;/code&gt; - all the other config changes&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="learnings"&gt;Learnings&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;It's faster to create a table with no index, copy data in, then add indices&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;random_page_cost=1.1&lt;/code&gt; for SSD backed databases works much better&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="snippets"&gt;Snippets&lt;/h2&gt;
&lt;h3 id="commands"&gt;Commands&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;\l&lt;/code&gt; list databases&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\c dbname&lt;/code&gt; connect to database as current user&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="create-table-as-copy-of-another"&gt;Create table as copy of another&lt;/h3&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FB4934;"&gt;create table&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt; new_table&lt;/span&gt;&lt;span style="color: #FB4934;"&gt; as table&lt;/span&gt;&lt;span&gt; old_table;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: this will copy all data, but no indices or constraints&lt;/p&gt;
&lt;p&gt;For no data&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FB4934;"&gt;create table&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt; new_table&lt;/span&gt;&lt;span style="color: #FB4934;"&gt; as table&lt;/span&gt;&lt;span&gt; old_table &lt;/span&gt;&lt;span style="color: #FB4934;"&gt;with no data&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you'd like to query/filter it:&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FB4934;"&gt;create table&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt; new_table&lt;/span&gt;&lt;span style="color: #FB4934;"&gt; as&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span style="color: #FB4934;"&gt;select&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; *&lt;/span&gt;&lt;span style="color: #FB4934;"&gt; from&lt;/span&gt;&lt;span&gt; old_table &lt;/span&gt;&lt;span style="color: #FB4934;"&gt;where&lt;/span&gt;&lt;span&gt; some_condition);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="check-for-waiting-locks"&gt;Check for waiting locks&lt;/h3&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FB4934;"&gt;select&lt;/span&gt;&lt;span&gt; relation::regclass, &lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;*&lt;/span&gt;&lt;span style="color: #FB4934;"&gt; from&lt;/span&gt;&lt;span&gt; pg_locks &lt;/span&gt;&lt;span style="color: #FB4934;"&gt;where not&lt;/span&gt;&lt;span&gt; granted;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="get-database-size"&gt;Get database size&lt;/h3&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FB4934;"&gt;SELECT&lt;/span&gt;&lt;span&gt; pg_size_pretty(pg_database_size(&lt;/span&gt;&lt;span style="color: #A89984;"&gt;'&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;database name&lt;/span&gt;&lt;span style="color: #A89984;"&gt;'&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="get-table-size"&gt;Get table size&lt;/h3&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FB4934;"&gt;SELECT&lt;/span&gt;&lt;span&gt; pg_size_pretty(pg_relation_size(&lt;/span&gt;&lt;span style="color: #A89984;"&gt;'&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;records&lt;/span&gt;&lt;span style="color: #A89984;"&gt;'&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="monitoring-replication-slots"&gt;Monitoring replication slots&lt;/h3&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;SELECT * FROM pg_replication_slots;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="monitoring-replication-lag"&gt;Monitoring replication lag&lt;/h3&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FB4934;"&gt;SELECT&lt;/span&gt;&lt;span&gt; extract(epoch &lt;/span&gt;&lt;span style="color: #FB4934;"&gt;from now&lt;/span&gt;&lt;span style="color: #A89984;"&gt;()&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; -&lt;/span&gt;&lt;span&gt; pg_last_xact_replay_timestamp&lt;/span&gt;&lt;span style="color: #A89984;"&gt;()&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span style="color: #FB4934;"&gt;AS&lt;/span&gt;&lt;span&gt; replica_lag&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="dump-database-schema-only"&gt;Dump database schema only&lt;/h3&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;pg_dump&lt;/span&gt;&lt;span style="color: #D3869B;"&gt; --schema-only&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt; databasename&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="useful-tools"&gt;Useful tools&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/wal-g/wal-g" rel="external"&gt;WAL streaming and backup via object storage&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Ellie's Notes</author><pubDate>Fri, 01 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ellie.wtf/notes/postgresql/</guid></item><item><title>Python</title><link>https://ellie.wtf/notes/python/</link><description>&lt;p&gt;My Python reference page&lt;/p&gt;
&lt;p&gt;I don't spend as much time building actual large projects in Python any more (though I was paid to write Python for a few years in my early career). These days it's mostly just for random glue scripts on a variety of systems.&lt;/p&gt;
&lt;h3 id="quick-s3-client"&gt;Quick s3 client&lt;/h3&gt;
&lt;p&gt;Setup an S3 client, with credentials loaded from the env. You actually don't need to explicitly list the creds, so long as you set &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; and &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt;. Magic ✨&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;import boto3&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;s3 = boto3.resource('s3')&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;for bucket in s3.buckets.all():&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;    print(bucket.name)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="a-generator-to-yield-chunks-of-a-fixed-size"&gt;A generator to yield chunks of a fixed size&lt;/h3&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FB4934;"&gt;def&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; chunks&lt;/span&gt;&lt;span style="color: #A89984;"&gt;(&lt;/span&gt;&lt;span style="color: #83A598;"&gt;lst&lt;/span&gt;&lt;span style="color: #A89984;"&gt;,&lt;/span&gt;&lt;span style="color: #83A598;"&gt; n&lt;/span&gt;&lt;span style="color: #A89984;"&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #A89984;"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;Yield successive n-sized chunks from lst.&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FB4934;"&gt;    for&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span style="color: #FB4934;"&gt; in&lt;/span&gt;&lt;span style="color: #FE8019;"&gt; range&lt;/span&gt;&lt;span style="color: #A89984;"&gt;(&lt;/span&gt;&lt;span style="color: #D3869B;"&gt;0&lt;/span&gt;&lt;span style="color: #A89984;"&gt;,&lt;/span&gt;&lt;span style="color: #FE8019;"&gt; len&lt;/span&gt;&lt;span style="color: #A89984;"&gt;(&lt;/span&gt;&lt;span style="color: #D5C4A1;"&gt;lst&lt;/span&gt;&lt;span style="color: #A89984;"&gt;),&lt;/span&gt;&lt;span style="color: #D5C4A1;"&gt; n&lt;/span&gt;&lt;span style="color: #A89984;"&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FB4934;"&gt;        yield&lt;/span&gt;&lt;span&gt; lst&lt;/span&gt;&lt;span style="color: #A89984;"&gt;[&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span style="color: #A89984;"&gt;:&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; +&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span style="color: #A89984;"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From &lt;a href="https://stackoverflow.com/questions/312443/how-do-i-split-a-list-into-equally-sized-chunks" rel="external"&gt;StackOverflow&lt;/a&gt;&lt;/p&gt;</description><author>Ellie's Notes</author><pubDate>Fri, 01 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ellie.wtf/notes/python/</guid></item><item><title>Resizing PVC for a Kubernetes Statefulset</title><link>https://ellie.wtf/notes/resizing-pvc-for-statefulset/</link><description>&lt;p&gt;If you've ever tried to resize a kubernetes statefulset PVC for a database deployment (or similar), you have probably seen this error&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Internal server error&lt;/p&gt;
&lt;p&gt;StatefulSet.apps "es-cluster" is invalid: spec: Forbidden: updates to statefulset &amp;gt; spec for fields other than 'replicas', 'template', and 'updateStrategy' are forbidden.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Edit all the PVCs manually: &lt;code&gt;kubectl edit pvc -n &amp;lt;namespace&amp;gt; &amp;lt;name&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Delete the StatefulSet, but leave the pods running: &lt;code&gt;kubectl delete -n &amp;lt;namesapce&amp;gt; statefulset --cascade=orphan &amp;lt;name&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Recreate the StatefulSet with &lt;code&gt;helm upgrade&lt;/code&gt;, &lt;code&gt;kubectl apply&lt;/code&gt;, or however else you deploy stuff&lt;/li&gt;
&lt;li&gt;Restart the StatefulSet to redeploy all the pods &lt;code&gt;kubectl rollout restart statefulset -n namespace &amp;lt;name&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;</description><author>Ellie's Notes</author><pubDate>Fri, 01 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ellie.wtf/notes/resizing-pvc-for-statefulset/</guid></item><item><title>Rust</title><link>https://ellie.wtf/notes/rust/</link><description>&lt;p&gt;My Rust reference page&lt;/p&gt;
&lt;h2 id="projects"&gt;Projects&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ellie.wtf/projects/atuin/"&gt;Atuin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="cargo"&gt;Cargo&lt;/h2&gt;
&lt;h3 id="run-just-integration-tests"&gt;Run just integration tests&lt;/h3&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;cargo test --test '*' &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="run-just-unit-tests"&gt;Run just unit tests&lt;/h3&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;cargo test --lib --bins&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="code"&gt;Code&lt;/h2&gt;
&lt;h2 id="git-sha-as-string-in-code"&gt;git sha as string in code&lt;/h2&gt;
&lt;p&gt;Useful for distribution, to report what revision was built. In &lt;code&gt;build.rs&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FB4934;"&gt;use&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt; std&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;::&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;process&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;::&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;Command&lt;/span&gt;&lt;span style="color: #A89984;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FB4934;"&gt;fn&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt; main&lt;/span&gt;&lt;span style="color: #A89984;"&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FE8019;"&gt;    let&lt;/span&gt;&lt;span style="color: #83A598;"&gt; output&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; =&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt; Command&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;::&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;new&lt;/span&gt;&lt;span style="color: #A89984;"&gt;(&amp;quot;&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;git&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;quot;)&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;.&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;args&lt;/span&gt;&lt;span style="color: #A89984;"&gt;([&amp;quot;&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;rev-parse&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;quot;, &amp;quot;&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;HEAD&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;quot;])&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;.&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;output&lt;/span&gt;&lt;span style="color: #A89984;"&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FE8019;"&gt;    let&lt;/span&gt;&lt;span style="color: #83A598;"&gt; sha&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; =&lt;/span&gt;&lt;span style="color: #FB4934;"&gt; match&lt;/span&gt;&lt;span style="color: #83A598;"&gt; output&lt;/span&gt;&lt;span style="color: #A89984;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;        Ok&lt;/span&gt;&lt;span style="color: #A89984;"&gt;(&lt;/span&gt;&lt;span style="color: #83A598;"&gt;sha&lt;/span&gt;&lt;span style="color: #A89984;"&gt;)&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; =&amp;gt;&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt; String&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;::&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;from_utf8&lt;/span&gt;&lt;span style="color: #A89984;"&gt;(&lt;/span&gt;&lt;span style="color: #83A598;"&gt;sha&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;.&lt;/span&gt;&lt;span&gt;stdout&lt;/span&gt;&lt;span style="color: #A89984;"&gt;)&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;.&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;unwrap&lt;/span&gt;&lt;span style="color: #A89984;"&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;        Err&lt;/span&gt;&lt;span style="color: #A89984;"&gt;(&lt;/span&gt;&lt;span style="color: #83A598;"&gt;_&lt;/span&gt;&lt;span style="color: #A89984;"&gt;)&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; =&amp;gt;&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt; String&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;::&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;from&lt;/span&gt;&lt;span style="color: #A89984;"&gt;(&amp;quot;&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;NO_GIT&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;quot;),&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #A89984;"&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #FABD2F;"&gt;    println!&lt;/span&gt;&lt;span style="color: #A89984;"&gt;(&amp;quot;&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;cargo:rustc-env=GIT_HASH=&lt;/span&gt;&lt;span style="color: #A89984;"&gt;{}&amp;quot;,&lt;/span&gt;&lt;span style="color: #83A598;"&gt; sha&lt;/span&gt;&lt;span style="color: #A89984;"&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #A89984;"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Usage:&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #FE8019;"&gt;const&lt;/span&gt;&lt;span style="color: #D3869B; font-weight: bold;"&gt; SHA&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt;: &amp;amp;&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt;str&lt;/span&gt;&lt;span style="color: #8EC07C;"&gt; =&lt;/span&gt;&lt;span style="color: #FABD2F;"&gt; env!&lt;/span&gt;&lt;span style="color: #A89984;"&gt;(&amp;quot;&lt;/span&gt;&lt;span style="color: #B8BB26;"&gt;GIT_HASH&lt;/span&gt;&lt;span style="color: #A89984;"&gt;&amp;quot;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h1 id="links"&gt;links&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/udoprog/leaky-bucket" rel="external"&gt;a leaky bucket rate limiter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Ellie's Notes</author><pubDate>Fri, 01 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ellie.wtf/notes/rust/</guid></item><item><title>Scaling Kube State Metrics</title><link>https://ellie.wtf/notes/scaling-kube-state-metrics/</link><description>&lt;p&gt;With larger clusters, you might find that scrapes begin to take time - &lt;a href="https://ellie.wtf/notes/kube-state-metrics/"&gt;kube state metrics&lt;/a&gt; might be exposing millions of samples, depending on how many nodes/pods/etc you have!&lt;/p&gt;
&lt;p&gt;KSM supports sharding, both &lt;a href="https://github.com/kubernetes/kube-state-metrics#automated-sharding" rel="external"&gt;automatically&lt;/a&gt; and manually. Unfortunately the autosharding is still very much in development&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Automatic sharding allows each shard to discover its nominal position when deployed in a StatefulSet which is useful for automatically configuring sharding. &lt;strong&gt;This is an experimental feature and may be broken or removed without notice.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, we could manually shard! This still isn't amazing though, as even if we have a bunch of pods scraping the same resource, they still each need to pull all the data. So we would go from having one pod pulling x data, to having n pods pulling x data. Or, &lt;code&gt;n * x&lt;/code&gt; total network transfer, rather than &lt;code&gt;n * x/n&lt;/code&gt; data transferred if we were only transferring what's required for that shard. Still not ideal.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Each shard decides whether the object is handled by the respective instance of kube-state-metrics or not. Note that this means all instances of kube-state-metrics, even if sharded, will have the network traffic and the resource consumption for unmarshaling objects for all objects, not just the ones they are responsible for.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The conclusion I've come to thus far, is to shard by data type. Pods tend to expose the most samples, so it makes sense to break those out first. So we have one deployment exposing pod metrics, and then one for everything else.&lt;/p&gt;
&lt;p&gt;It seems like this is what &lt;a href="https://www.datadoghq.com/blog/engineering/our-journey-taking-kubernetes-state-metrics-to-the-next-level/" rel="external"&gt;has been done elsewhere&lt;/a&gt;, and works for now&lt;/p&gt;</description><author>Ellie's Notes</author><pubDate>Fri, 01 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ellie.wtf/notes/scaling-kube-state-metrics/</guid></item><item><title>Systemd could not open shared memory segment</title><link>https://ellie.wtf/notes/systemd-could-not-open-shared-memory-segment/</link><description>&lt;p&gt;I spent a while operating &lt;a href="https://ellie.wtf/notes/postgresql/"&gt;PostgreSQL&lt;/a&gt; running on an EC2 instance. We had a weird problem where sometimes queries would fail, with the error&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;could not open shared memory segment "/PostgreSQL.271741757"&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For non-system (UID &amp;lt; 1000) users, &lt;code&gt;logind&lt;/code&gt; will by default clear shared mem files upon logout. For things like databases, this is not at all desireable. One thing to note is that it's unlikely for this to happen if you never login as the database user.&lt;/p&gt;
&lt;p&gt;Setting &lt;code&gt;RemoveIPC=no&lt;/code&gt; in &lt;code&gt;/etc/systemd/logind.conf&lt;/code&gt; should resolve this. In theory, you can restart logind while the system is running. I did some tests, but I think it's still probably safest to reboot the system.&lt;/p&gt;
&lt;p&gt;Man page&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;RemoveIPC=&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;           Controls whether System V and POSIX IPC objects belonging to&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;           the user shall be removed when the user fully logs out. Takes&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;           a boolean argument. If enabled, the user may not consume IPC&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;           resources after the last of the user's sessions terminated.&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;           This covers System V semaphores, shared memory and message&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;           queues, as well as POSIX shared memory and message queues.&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;           Note that IPC objects of the root user and other system users&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;           are excluded from the effect of this setting. Defaults to&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;           &amp;quot;yes&amp;quot;.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once changed, a full reboot will ensure the config is loaded. Alternatively, a restart of &lt;code&gt;logind&lt;/code&gt; should also resolve this: &lt;code&gt;systemctl restart systemd-logind.service&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Useful links:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;https://github.com/systemd/systemd/issues/4532&lt;/li&gt;
&lt;li&gt;https://github.com/systemd/systemd/issues/2039&lt;/li&gt;
&lt;li&gt;https://stackoverflow.com/questions/49065733/redhat-or-centos7-systemd-may-remove-user-ipc-resources-unexpectedly&lt;/li&gt;
&lt;li&gt;https://stackoverflow.com/questions/73368759/why-systemd-remove-my-shm-file-but-not-postgresqls&lt;/li&gt;
&lt;/ul&gt;</description><author>Ellie's Notes</author><pubDate>Fri, 01 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ellie.wtf/notes/systemd-could-not-open-shared-memory-segment/</guid></item><item><title>Trigger Alertmanager Manually</title><link>https://ellie.wtf/notes/trigger-alertmanager-manually/</link><description>&lt;p&gt;I was setting up Alertmanager and needed to manually trigger an alert to make sure it works&lt;/p&gt;
&lt;p&gt;Something like this will do it, depending on your port:&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;curl -H 'Content-Type: application/json' -d '[{&amp;quot;labels&amp;quot;:{&amp;quot;alertname&amp;quot;:&amp;quot;test-alert&amp;quot;}}]' http://alertmanager:9093/api/v1/alerts&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;-&amp;gt; {&amp;quot;status&amp;quot;: &amp;quot;success&amp;quot;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</description><author>Ellie's Notes</author><pubDate>Fri, 01 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ellie.wtf/notes/trigger-alertmanager-manually/</guid></item><item><title>Tuning ZFS for Postgres</title><link>https://ellie.wtf/notes/tuning-zfs-postgres/</link><description>&lt;ul&gt;
&lt;li&gt;recordsize=128 not that bad, actually&lt;/li&gt;
&lt;li&gt;preferring a scan to index for some reason. &lt;code&gt;random_page_cost = 1&lt;/code&gt; sorted that
&lt;ul&gt;
&lt;li&gt;worked after running analyze and a couple of queries&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description><author>Ellie's Notes</author><pubDate>Fri, 01 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ellie.wtf/notes/tuning-zfs-postgres/</guid></item><item><title>Keeping notes in public</title><link>https://ellie.wtf/posts/public-notes/</link><description>&lt;blockquote class="markdown-alert-note"&gt;
&lt;p&gt;An update
I liked this so much that I actually replaced my whole blog with it! Now my blog is just a part of the whole. Quartz updated around the time I wrote this, and it solved a bunch of my frustrations, so I'm using it now.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I first blogged about having a personal wiki more than &lt;a href="https://ellie.wtf/ive-started-a-personal-wiki/" rel="external"&gt;three years ago&lt;/a&gt; now. It was a nice try at solving the "I'd like to keep notes in public" problem, but it still didn't really do it for me. I kept it up to date for about a month, and then it was abandoned.&lt;/p&gt;
&lt;p&gt;I'd built it with Hugo, a static site generator. The setup was pretty smooth and only required me to edit/update some markdown.&lt;/p&gt;
&lt;p&gt;I've tried static site generators &lt;em&gt;so many times&lt;/em&gt;, but I've never been truly happy with them. I think because it feels too much like writing code, and I get too distracted trying to automate my setup or tweak settings or whatever. I also don't blog frequently enough to remember all the front matter options, and end up having to check the docs far too much.&lt;/p&gt;
&lt;p&gt;I've seen games developers say to "make games, not engines" and I felt like I was just making an engine.&lt;/p&gt;
&lt;p&gt;Anyways. I've been using &lt;a href="https://obsidian.md/?ref=ellie.wtf" rel="external"&gt;Obsidian&lt;/a&gt; for a while, and decided I'd try it out for a public wiki. To begin with I tried a few setups using static site generators like &lt;a href="https://quartz.jzhao.xyz/?ref=ellie.wtf" rel="external"&gt;Quartz&lt;/a&gt;, and the Obsidian &lt;a href="https://github.com/denolehov/obsidian-git?ref=ellie.wtf" rel="external"&gt;git sync plugin&lt;/a&gt;. But again, I found myself tinkering with setups and not just taking some notes. Maybe someday I'll go for something more custom, but for now that's not the point.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://obsidian.md/publish?ref=ellie.wtf" rel="external"&gt;Obsidian Publish&lt;/a&gt; solved the problem really nicely for me, and I've been happy with it for the past month or two! I usually update it a few times a week, and I really like the way it presents things.&lt;/p&gt;
&lt;p&gt;I'm still playing with how I organize things a bit (one big page I keep adding to with new things, or lots of little notes?), but what matters most is that I'm actually writing things down.&lt;/p&gt;</description><author>Ellie's Notes</author><pubDate>Fri, 01 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ellie.wtf/posts/public-notes/</guid></item><item><title>On cognitive taxes</title><link>https://holzer.online/weeknotes/2023/35/index.html</link><description>&lt;p&gt;
I spent a good deal of the work week convincing myself that multiple layers of data mapping from one anemic domain model to another were indeed soundly and safely removeable. I generally try to apply Chesterton's fence when dealing with inherited code. Each layer between system boundary and internal domain model had a legitimate function, which I indeed could push down into the internal model, making it slightly less anemic. Through a lot of (partly self-, partly framework-induced) indirection the functionality was very hard to actually see, which created a hefty cognitive tax. In this particular case, that also is not a mere figure of speech, for the attempt of a rather mundane (and outwardly transparent) change caused an ungodly amount of burned engineering time. 
&lt;/p&gt;

&lt;p&gt;
At home I don't have so much uninterupted leisure time (emphasis on uninterupted) available, which means for most books of my choice I need quite some time to get through. But last week I managed to read Rolf Dobellis &lt;i&gt;Die Kunst des klugen Handelns&lt;/i&gt; ("The art of acting wisely" - don't know if an english translation is published) within three days. It is about cognitive biases and errors in thinking and consists of mercifully short chapters, which fitted quite neatly into many fractured timeslots, that would otherwise have been rather dead. 
&lt;/p&gt;

&lt;p&gt;
Also I'm really starting to feel jaded by Substack based "blog-letters" (or how shall one call nominally newsletters with fully accessible public archives?). I hate it when the reading flow is interrupted by their annoying overlay, which is added very gradually while scrolling down, so that while you read, you know that if you want to continue, you'll have to dismiss the same damned dialog all over again in a few seconds. It totally ruptures concentration and flow. Yeah, I know, creators want to capture their audience, and free is hardly ever really free, we all have to pay our bills, but this particualar interruption in the middle of articles starts to feel for me like being hit with a hammer. There is ample evidence that forcing the brain to make decisions leads to cognitive fatigue and depletes the abilities for self-control. I'm picking on Substack here, because they are currently very popular in my online bubble, but the same could be said about many other websites and platforms. I find myself wondering, if there were a filter mechanism for all sites that apply practices that are in the end hostile to my brain, would I actually be missing out? And even if that were the case, wouldn't I be better off anyway?
&lt;/p&gt;

&lt;time datetime="2023-09-01" title="That's 2023-09-01 for Non-Discordians"&gt;
Prickle-Prickle, the 25th day of Bureaucracy in the YOLD 3189
&lt;/time&gt;</description><author>Fabian's public notepad</author><pubDate>Fri, 01 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://holzer.online/weeknotes/2023/35/index.html</guid></item><item><title>September 2023 - Bookmarks</title><link>https://domenicoluciani.com/2023/09/01/bookmarks.html</link><description>Bookmarks for September 2023: 11 links - Measuring developer productivity? A respon...; How to pass a coding interview with me - R..., and more.</description><author>Domenico Luciani</author><pubDate>Fri, 01 Sep 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://domenicoluciani.com/2023/09/01/bookmarks.html</guid></item><item><title>Embracing Failure in Software Development</title><link>https://www.danielcorin.com/posts/2023/on-failure/</link><description>Exploring the Importance of Embracing Failure in Software Development and the Value of Learning from Mistakes</description><author>Thought Eddies</author><pubDate>Thu, 31 Aug 2023 23:09:00 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/posts/2023/on-failure/</guid></item><item><title>Introduction to the OWASP Top 10 (at a former employer in May 2023)</title><link>https://brntn.me/blog/talk-introduction-to-the-owasp-top-10-at-goodhuman-in-may-2023/</link><description>&lt;blockquote&gt;
&lt;p&gt;This is an edited transcript of a talk I gave at &lt;a href="https://www.afr.com/work-and-careers/workplace/ndis-provider-goodhuman-forced-into-liquidation-owing-nearly-3m-20240308-p5fawd"&gt;GoodHuman&lt;/a&gt; about the OWASP Top 10. The talk is available in my &lt;a href="https://github.com/sesh/quick-presentations"&gt;quick-presentations&lt;/a&gt; repository.&lt;/p&gt;
&lt;p&gt;The transcript was created OpenAI's Whisper API, then edited by myself and Claude2 using a similar strategy to &lt;a href="https://simonwillison.net/2023/Aug/6/annotated-presentations/"&gt;Simon Willison&lt;/a&gt;. Screenshots were created using &lt;a href="https://shot-scraper.datasette.io/en/stable/"&gt;shot-scraper&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;EDIT 2024-02-26: GoodHuman owes me pay from June/July 2023, so I've updated these slides to remove their brand colours. I've also updated the link above that previously went to GoodHuman to go somewhere else.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.0.png" /&gt;&lt;/p&gt;
&lt;p&gt;Good morning. Today I'm going to cover the OWASP Top 10.
If you're not across the OWASP Top 10, that's fine. This is just a super high-level introduction. I'm going to run through each of the 10 items on the list and then talk about some public exploits for each one.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.1.png" /&gt;&lt;/p&gt;
&lt;p&gt;So what is the &lt;a href="https://owasp.org/www-project-top-ten/"&gt;OWASP Top 10&lt;/a&gt;? It's a regularly updated list of the most critical security risks to web applications.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.2.png" /&gt;&lt;/p&gt;
&lt;p&gt;It's produced by the Open Worldwide Application Security Project.&lt;/p&gt;
&lt;p&gt;That's a group of about 50,000 security professionals who have joined as members. And you &lt;a href="https://owasp.org/membership/"&gt;can be a member&lt;/a&gt;. It only costs $50 to join for a year, which helps support the projects that they do.&lt;/p&gt;
&lt;p&gt;They get together for in-person events where they talk about this stuff. But they also have a really huge online community of security-focused web developers and security professionals.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.3.png" /&gt;&lt;/p&gt;
&lt;p&gt;Here's the top 10. I'm just going to dive into this.&lt;/p&gt;
&lt;p&gt;But first I want to just caveat it all a little bit. For each of these items in the top 10, you could probably spend an entire day doing a workshop on them. And I'm going to try to spend two minutes on each one. So that's the depth that we're going to get to here.&lt;/p&gt;
&lt;p&gt;I'm hoping to give you the exposure to what they are so that you can start to think about these things and how they apply within our application and the code that you're writing.&lt;/p&gt;
&lt;p&gt;Let's jump in.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.4.png" /&gt;&lt;/p&gt;
&lt;p&gt;Number one on the list is broken access control, which is any situation where a user can access something that's outside of their intended set of permissions. We see that happen all the time. It's number one on the list because it's incredibly common and it can have a really high impact. There's a high risk of data exposure when you allow users to access things that they're not supposed to.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.6.png" /&gt;&lt;/p&gt;
&lt;p&gt;We see it happening through things like manipulating URLs, changing the contents of cookies, changing the contents of JWTs or how they're signed, and just generally manipulating things to do with an HTTP request that we're making to a service.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.7.png" /&gt;&lt;/p&gt;
&lt;p&gt;We also see missing access control on unexpected HTTP methods. This is a specific type of web vulnerability where, say, you've got the access control on the GET request, but then someone makes a PATCH request and that circumvents the access control and still returns the object.&lt;/p&gt;
&lt;p&gt;Things like that can be really common in web frameworks where you're relying on the framework to do a lot of that access control and checking.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.8.png" /&gt;&lt;/p&gt;
&lt;p&gt;This is an example of what you might see in the network tab of Google Chrome or Firefox. If I ever see this URL, there's a 100% chance that I'm going to change that final 2 to a 3 and just see what happens.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.9.png" /&gt;&lt;/p&gt;
&lt;p&gt;And in the case of Optus, what might happen is you return the full customer record for that next customer ID, regardless of who you're authenticated as.&lt;/p&gt;
&lt;p&gt;For each of these, I'm going to try to give you a brief example of how this has gone catastrophically wrong for somebody. And in the case of Optus, they had a public API endpoint to get customer information that took a customer ID and didn't require any authentication. Someone found that, just enumerated through all of the sequential customer IDs, and saved that data.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.10.png" /&gt;&lt;/p&gt;
&lt;p&gt;Number two is cryptographic failures.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.11.png" /&gt;&lt;/p&gt;
&lt;p&gt;Effectively, just bad cryptography that leads to unexpected data exposure, which is a pretty broad statement. And it's actually a really big category of different types of vulnerabilities.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.12.png" /&gt;&lt;/p&gt;
&lt;p&gt;It encompasses things like weak TLS configurations that allow a person-in-the-middle attack. So that's having bad ciphers or having a weakly generated SSL certificate.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.13.png" /&gt;&lt;/p&gt;
&lt;p&gt;Sticking with SSL, it also covers the HSTS header. If you're not across what the HSTS header does, it sets an amount of time that once a browser has accessed your website over HTTPS, it should never allow HTTP connections. If you don't set it up properly, you can suffer from downgrade attacks where an attacker will send the user to the HTTP version of your website and intercept that traffic.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.14.png" /&gt;&lt;/p&gt;
&lt;p&gt;Another huge one here is the use of default passwords and secrets in the application. I'll share the slides later, but these four links down here are just to GitHub repos full of default passwords for things like routers and MongoDB instances and fresh installs of PHP apps and stuff like that. There are so many data breaches that happen because people just credential stuff with the default password for applications that are publicly available on the internet and then dump everything out of it.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.15.png" /&gt;&lt;/p&gt;
&lt;p&gt;It also covers the use of weak algorithms and keys with low entropy. So that's using bad password hashing algorithms.&lt;/p&gt;
&lt;p&gt;You should just use Bcrypt or Argon2 if you need to hash a password or do what we do and rely on Firebase, Auth0 or someone that gets paid a huge amount of money to get that stuff right.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.16.png" /&gt;&lt;/p&gt;
&lt;p&gt;Accidental key exposure falls into this as well. With accidental key exposure, my example is from March of this year where GitHub accidentally included their RSA private key in a public GitHub repo.&lt;/p&gt;
&lt;p&gt;Heaps of people seem to have had access to that key, which is a bit scary considering that that's all you would have needed to intercept all SSH traffic to GitHub or to enable interception of SSH traffic to GitHub.&lt;/p&gt;
&lt;p&gt;One big thing that they got called out for was the sentence &amp;quot;and out of an abundance of caution,&amp;quot; they replaced the RSA key. They leaked the RSA key publicly. It's not an abundance of caution. The only behaviour that you can do after that happens is rotating the key.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.17.png" /&gt;&lt;/p&gt;
&lt;p&gt;Everyone web developer's favourite: Injection.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.18.png" /&gt;&lt;/p&gt;
&lt;p&gt;Injection is a situation where user supplied data is directly injected into your code. You're allowing a user to change the behaviour of your application when they submit data.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.19.png" /&gt;&lt;/p&gt;
&lt;p&gt;Obviously SQL injection is something that we're all probably pretty aware of, but it's not just SQL that you have to care about here. Things like LDAP, NoSQL, if you're piping commands to a system command line to generate a PDF or something like that, that can also be vulnerable to injection and ORMs are not immune either. There are plenty of CVEs and pretty critical ones as well for ORM exploits that involve injection.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.20.png" /&gt;&lt;/p&gt;
&lt;p&gt;The example is the Bobby Tables XKCD comic. It's about a mom that happened to name her son, Robert, quote, close bracket, colon drop table students, colon dash dash. And the school had a heap of problems with that name and yeah, that's the XKCD comic.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.21.png" /&gt;&lt;/p&gt;
&lt;p&gt;Insecure design is next on the list. And it covers design and architectural flaws in your system.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.22.png" /&gt;&lt;/p&gt;
&lt;p&gt;It's one that was introduced in 2021 because they want teams thinking more about the idea of secure by design, doing activities like threat modelling to understand the risks of changes that you're introducing and improving the security posture through training and through better design of the application that you're working on.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.23.png" /&gt;&lt;/p&gt;
&lt;p&gt;Pretty much all of these things are design flaws, even something as simple as a SQL injection, right? It's like you've designed that API endpoint to allow the user to do that. And you can pick those things up at the design phase through better practices around security, but also through understanding the risks that you're potentially introducing.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.24.png" /&gt;&lt;/p&gt;
&lt;p&gt;Huge, huge example here from November last year is LastPass. I link at the end to all of the examples and the LastPass security incident section on Wikipedia is probably longer than the rest of the article combined. It's huge.&lt;/p&gt;
&lt;p&gt;The issue that they had and what I would definitely classify as bad design in 2022 is someone got in and downloaded a whole bunch of backups. Backups of customer password vaults. And you would think, that's not the end of the world. They're encrypted with my master password or with my password that I've supplied to sort of add into the encryption key...&lt;/p&gt;
&lt;p&gt;The design flaw that they had was that only the passwords in the vault were encrypted. So now the attackers have a massive list of username / email address and account and where you have accounts based on what they were able to get out of those backups.&lt;/p&gt;
&lt;p&gt;And then there was a secondary design flaw which makes things even worse, which is effectively that if you hadn't changed your master password in the last few years, then it had only gone through a thousand rounds of hashing with their hashing algorithm, which meant that they were quite easy to crack.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.25.png" /&gt;&lt;/p&gt;
&lt;p&gt;Next on the list, security misconfiguration, which is just like applications that are improperly secured because of a misconfiguration.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.26.png" /&gt;&lt;/p&gt;
&lt;p&gt;Classic one in the web development world is like MongoDB that's open to the internet and it can grab everything out of it.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.27.png" /&gt;
This is also things where you've missed steps in like your go-live list or the hardening documentation of a piece of software that you're adding into your infrastructure.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.28.png" /&gt;
And also situations where you might accidentally deploy to production with development configs. The example I used a couple of years ago when I did a presentation about this was like Twitter deploying with source maps and the full unmodified source into production and things like that. Not something that you necessarily want do when you're a company that size.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.29.png" /&gt;&lt;/p&gt;
&lt;p&gt;Another potential example here is features that aren't being used, features that are enabled that aren't being used that increase the attack surface. So that's enabling things that are still a work in progress or haven't been properly secured.&lt;/p&gt;
&lt;p&gt;That kind of goes into security by design and baking in security from day zero to make sure that when you enable something or if something gets accidentally enabled, you're not increasing the attack surface.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.30.png" /&gt;&lt;/p&gt;
&lt;p&gt;An example here, and this has hit companies here in Australia, is this article from Krebs about how many public Salesforce sites are leaking private data.&lt;/p&gt;
&lt;p&gt;Effectively, there's a default configuration with public Salesforce sites that if you allow anonymous users to write data, then those anonymous users are able to read data as well, including data submitted by other anonymous users. So if you spin up a little site to just do a quiz or get some feedback from people or something like that, the anonymous users are able to just change the URL and see all of their responses.&lt;/p&gt;
&lt;p&gt;That's like the default configuration and this has hit banks and healthcare providers and government agencies who just pay consultants a heap of money to deploy a Salesforce site for something and then it doesn't get secured properly.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.31.png" /&gt;&lt;/p&gt;
&lt;p&gt;Vulnerable and outdated components.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.32.png" /&gt;&lt;/p&gt;
&lt;p&gt;So applications using components with known security vulnerabilities, you probably have all seen lthe of Dependabot and things like that before to detect vulnerabilities that are out of date and in particular detect vulnerabilities that have known security issues, which you need to update.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.33.png" /&gt;&lt;/p&gt;
&lt;p&gt;This one will cover both accidental vulnerabilities and malicious changes. So in the case of open source projects, I suppose generally developers aren't intending to introduce vulnerabilities, so it's an accidental vulnerability, but malicious changes definitely do happen.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.34.png" /&gt;&lt;/p&gt;
&lt;p&gt;When a malicious change is specifically targeting a set of users, that's become known as a supply chain attack where someone is specifically targeting you by modifying a dependency in your supply chain. And that's something that we've seen a lot of in the last three or four years.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.35.png" /&gt;&lt;/p&gt;
&lt;p&gt;The example here is the node IPC library. In March 2022 after the Russian invasion of Ukraine, the developer updated that library to just delete files if you happen to install it from within Russia. We might support that action but it's something that you don't want to accidentally run in your build pipeline or on a build server or in your production environment where someone's package that they've updated deletes a bunch of files off of your computer.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.36.png" /&gt;&lt;/p&gt;
&lt;p&gt;Identification and authentication failures which is allowing the wrong user to authenticate to a system.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.37.png" /&gt;&lt;/p&gt;
&lt;p&gt;And that might be a malicious user or it might just be a user who has logged into one system and gets access to another system unintentionally. This covers things like account enumeration, which is something that I've seen a bunch of in the past with companies that I've worked for where attackers have just tried to log in with a whole bunch of leaked credentials.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.38.png" /&gt;&lt;/p&gt;
&lt;p&gt;Account enumeration becomes a big deal for companies when you allow customers to have weak and previously leaked passwords. So being able to check against or being able to check password entropy when you're setting or changing the password is one way to combat this, but also checking against public databases of leaked passwords is a great action that we can do.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.39.png" /&gt;&lt;/p&gt;
&lt;p&gt;This one also covers misconfiguration of authentication. So that's things like being able to bypass MFA. So if you've got a username and password for a customer, but the customer has MFA, hopefully that stops the attacker from being able to log in, but there are vulnerabilities where you can figure out MFA bypasses. That's sort of one where you'd be joining multiple attacks together, but it's something that we do see in the wild. That's what happened to Uber when they had one of their employees get hacked and they bypassed MFA to get access to their network and then found PowerShell scripts full of tokens that had access to customer data.&lt;/p&gt;
&lt;p&gt;OAuth scope creep. So that's where when someone logs in via OAuth and the attacker changes the scopes that are given. So you're logging in with a proper account, but you end up generating a token that has more privilege than you're expecting.&lt;/p&gt;
&lt;p&gt;And then unverified user registrations, which is something that I'm sure we are handling quite well, but it's situations where a user is able to register a new account, but they're not supposed to be.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.40.png" /&gt;&lt;/p&gt;
&lt;p&gt;The example here is not really that much of an example, but if you haven't seen the Have I Been Pwned website, I linked to it at the end and I definitely recommend to go and have a look and enter your personal email address. For myself, my email address is in 19 data breaches on the site. Everyone's details are on the internet and we just have to deal with that.&lt;/p&gt;
&lt;p&gt;Using MFA and unique passwords is the best way to combat data breaches like these.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.41.png" /&gt;&lt;/p&gt;
&lt;p&gt;Software and data integrity failures&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.42.png" /&gt;&lt;/p&gt;
&lt;p&gt;So this was another one that was introduced in 2021 and it's about pipelines and assuming that the build that you just tested and built on your like build environment is actually what ends up being deployed.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.43.png" /&gt;&lt;/p&gt;
&lt;p&gt;This also covers using dependencies from third parties without verifying the contents of them. So when we find a great third party library to do something, do we actually go and have a look at the source code and make sure that it's not doing something stupid?&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.44.png" /&gt;&lt;/p&gt;
&lt;p&gt;And then also this covers allowing untrusted code into like your ecosystem. So that's whether it's a dependency that you added or a new component that you're putting into your system. How much do you trust that piece of code?&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.45.png" /&gt;&lt;/p&gt;
&lt;p&gt;I'm not going to go into too much detail here about the SolarWinds attack because it's pretty old now, but the SolarWinds hack was a hack by Russian government hackers really that attacked a whole bunch of US companies and government agencies by updating the firmware on a network device. And the network device wasn't properly checking the signature of the firmware before updating. And then they were just sniffing all the traffic. It's definitely one to go and read about - there are books written about this hack. It's a really great one to dive deep into.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.46.png" /&gt;&lt;/p&gt;
&lt;p&gt;Next up is security logging and monitoring failures.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.47.png" /&gt;&lt;/p&gt;
&lt;p&gt;So this one covers not having enough information to investigate an issue. So when a security incident does occur, do you have enough information to figure out what the attacker actually had access to, how they got in, what customer information was exposed and those sorts of things?&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.48.png" /&gt;&lt;/p&gt;
&lt;p&gt;And then it also covers logging too much information. So if the attacker does get in and all they've got access to is your logs, but they're able to scrape your logs for customer information and passwords, that would be bad.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.49.png" /&gt;&lt;/p&gt;
&lt;p&gt;An example of this, probably more the former, but could also end up being the latest Latitude Pay hack earlier this year, where initially they came out and announced that they'd been attacked and they think about 15,000 customer records had been accessed. And then a week later, it was 300,000. And then two weeks after that, it was seven or 8 million people. And that's just an example of they didn't have the information at hand to understand the impact level of the incident. And that sort of really dictates how you handle an incident like that.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.50.png" /&gt;&lt;/p&gt;
&lt;p&gt;Lucky last on the list is server-side request forgery. It's kind of an odd one on the list here because it's very specific. And I think if you look at the OWASP Top 10 page, this one was added through a community survey. It is a really important vulnerability to understand.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.51.png" /&gt;&lt;/p&gt;
&lt;p&gt;SSRF is effectively allowing your server to make requests to a resource provided by a user. And you see that manifest itself in things like making requests to reveal metadata about the service.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.52.png" /&gt;&lt;/p&gt;
&lt;p&gt;A common attack is to make requests to the AWS metadata IP address from screenshot services. That would dump out a screenshot of the JSON file that includes the AWS tokens for that service and things like that.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.53.png" /&gt;&lt;/p&gt;
&lt;p&gt;You can use this to trick servers into mining cryptocurrencies and stuff like that. So if you've got a form that accepts a URL and you enter a URL that's got a cryptocurrency miner, maybe that will just sit there and do that forever.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.54.png" /&gt;&lt;/p&gt;
&lt;p&gt;The couple of examples here. So this URL, if anyone in the chat can tell me what that's actually gonna access, I'd love to know.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.55.png" /&gt;&lt;/p&gt;
&lt;p&gt;And this URL, if anyone knows what that one accesses, both perfectly valid URLs and both things that you'll see in a pen test, I think.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.56.png" /&gt;&lt;/p&gt;
&lt;p&gt;And the example here is this one from a hacker named Orange. I've linked to it at the end, but they basically took a pretty simple SSRF in GitHub Enterprise's handling of webhooks and turned that into a remote code exploit in Memcached within the GitHub Enterprise deployment. And got tens of thousands of dollars from the GitHub bug bounty for what was effectively just a really simple SSRF, what began as a really simple SSRF attack.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.57.png" /&gt;&lt;/p&gt;
&lt;p&gt;Well, I'm 54 seconds over, but here's my super quick wrap up.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.58.png" /&gt;&lt;/p&gt;
&lt;p&gt;The OWASP Top 10 is amazing. You should go and read it. It's highly, highly accessible. It's super straightforward to read through, but it's really only the first 10 of heaps of issues that we have to care about as web developers. So hopefully we can keep talking about this stuff more in the future.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.59.png" /&gt;&lt;/p&gt;
&lt;p&gt;The advice that I want to leave everyone with is really just to think about security as early as possible during the planning and design phase of the work that we're doing and make sure that it's something that we're considering. And that's it.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.60.png" /&gt;&lt;/p&gt;
&lt;p&gt;Resources if you want to learn more&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://owasp.org/"&gt;OWASP&lt;/a&gt; (&lt;a href="https://owasp.org/www-project-top-ten/"&gt;Top 10&lt;/a&gt;, &lt;a href="https://cheatsheetseries.owasp.org/"&gt;Cheat Sheet Series&lt;/a&gt;, &lt;a href="https://owasp.org/www-project-web-security-testing-guide/stable/"&gt;Testing Guide&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pentesterlab.com/bootcamp"&gt;Pentester Labs Bootcamp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.picoctf.org/"&gt;PicoCTF&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://risky.biz/"&gt;Risky Business&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.61.png" /&gt;&lt;/p&gt;
&lt;p&gt;Exposure links&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://verse.systems/blog/post/2022-09-25-optus-breach/"&gt;Optus breach details&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.blog/2023-03-23-we-updated-our-rsa-ssh-host-key/"&gt;Github RSA key leak&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://xkcd.com/327/"&gt;Exploits of a Mom&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/LastPass#Security_incidents"&gt;Lastpass Security Incidents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://krebsonsecurity.com/2023/04/many-public-salesforce-sites-are-leaking-private-data/"&gt;Salesforce public sites&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.theregister.com/2022/03/18/protestware_javascript_node_ipc/"&gt;node-ipc updated to support Ukraine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://haveibeenpwned.com"&gt;Have I Been Pwned?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.wired.com/story/russia-solarwinds-hack-roundup/"&gt;Solarwinds&lt;/a&gt; (&lt;a href="https://archive.md/3RizK"&gt;non-paywalled link&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ia.acs.org.au/article/2023/latitude-breach-just-got-much-worse.html"&gt;Latitude breach just got much worse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.orange.tw/2017/07/how-i-chained-4-vulnerabilities-on.html"&gt;Github SSRF Exploit Chain&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="" src="https://media.brntn.me/owasp-top10.62.png" /&gt;&lt;/p&gt;</description><author>Brenton Cleeland</author><pubDate>Thu, 31 Aug 2023 17:00:00 GMT</pubDate><guid isPermaLink="true">https://brntn.me/blog/talk-introduction-to-the-owasp-top-10-at-goodhuman-in-may-2023/</guid></item><item><title>Ragebuilding a startup to success</title><link>https://anyfactor.github.io/posts/ragebuilding_startup/</link><description>&lt;p&gt;Rage-building a startup to success is a thing and it is undoubtedly the best and most guranteed way to start building.&lt;/p&gt;</description><author>Anyfactor's blog</author><pubDate>Thu, 31 Aug 2023 14:11:14 GMT</pubDate><guid isPermaLink="true">https://anyfactor.github.io/posts/ragebuilding_startup/</guid></item><item><title>PET 2001 Emulator: Option ROMs</title><link>https://www.masswerk.at/nowgobang/2023/pet-option-roms</link><description>Just to let you know, in case you were interested...</description><author>mass:werk – Now Go Bang!</author><pubDate>Thu, 31 Aug 2023 09:50:00 GMT</pubDate><guid isPermaLink="true">https://www.masswerk.at/nowgobang/2023/pet-option-roms</guid></item><item><title>On Grind - from Investor to Creator to Founder</title><link>https://www.swyx.io/on-grind</link><description>Sarah asks a provoking question that has been on my mind a lot as I transition from part time creator to founder:</description><author>swyx's site RSS Feed</author><pubDate>Thu, 31 Aug 2023 09:21:01 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/on-grind</guid></item><item><title>The Bookmarks Bin, September 2023</title><link>https://maximumeffort.substack.com/p/the-bookmarks-bin-september-2023</link><description>Shamelessly copying Scott Alexander's link compilation articles</description><author>Maximum Effort, Minimum Reward</author><pubDate>Thu, 31 Aug 2023 09:09:07 GMT</pubDate><guid isPermaLink="true">https://maximumeffort.substack.com/p/the-bookmarks-bin-september-2023</guid></item><item><title>Pseudonymity and the point of no return</title><link>https://voussoir.net/writing/online_pseudonymity</link><description>&lt;article&gt;
&lt;p&gt;&lt;a href="/writing"&gt;Back to writing&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a class="tag_link" href="/writing/tags/introspection"&gt;[introspection]&lt;/a&gt; &lt;a class="tag_link" href="/writing/tags/life"&gt;[life]&lt;/a&gt; &lt;a class="tag_link" href="/writing/tags/photography"&gt;[photography]&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Pseudonymity and the point of no return&lt;/h1&gt;
&lt;p&gt;A conundrum. As usual. I must warn you this article is a self-contradictory stream of consciousness mess.&lt;/p&gt;
&lt;p&gt;I was raised in the &lt;a href="https://www.youtube.com/watch?v=TpNfQlcIjAM" title="only 90s kids"&gt;era&lt;/a&gt; where "don't share your real name online" was a standard rule. Pseudonymity practically paramount. I'm not old enough to have taken part in myspace, but I am old enough to have seen the rise of facebook and twitter.&lt;/p&gt;
&lt;p&gt;In traditional web forums of the &lt;a href="https://en.wikipedia.org/wiki/VBulletin"&gt;vBulletin&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/phpBB"&gt;phpBB&lt;/a&gt; kind, and in online video games, it is customary to use a pseudonym and an avatar that is not a photo of yourself. Of course, the word "avatar" by definition means something that represents you but isn't literally you. Nowadays, websites don't ask for an avatar, but rather a "profile picture". They want names and faces, and plenty oblige.&lt;/p&gt;
&lt;p&gt;When I say that the rule is "don't share your real name online", I don't just mean "don't connect it to your favorite pseudonym" or "make a separate profile with your real name". I mean don't share it at all. As in, you should always only use a pseudonym and there should be zero search results for your real name. That's difficult to guarantee, because any time you participate in a local club, competition, or event, there's a chance you'll be named in a roster or announcement that goes online. Where did I get this rule, anyway? I don't know. That's just how childhood programming works, I guess.&lt;/p&gt;
&lt;p&gt;With a few exceptions, this is how I've lived my online life. It stresses me out when my name is published anywhere, especially linked to a physical place like a city or school, because it violates this rule that I have, thus far without enough question, taken as truth. If that doesn't sound stressful to you, perhaps you could imagine finding naked pictures of yourself shared online. If that still doesn't sound stressful, you must already be very popular.&lt;/p&gt;
&lt;p&gt;But isn't it foolish for me to call y2k-era forum pseudonymity "traditional"? This is such an obvious bias on my part. People were posting on Usenet &lt;a href="/writing/online_pseudonymity/usenet_berlin_wall.html"&gt;while the Berlin wall was coming down&lt;/a&gt;, which I was startled to learn. They wrote under their full names, and some even signed off with a postal address. I hope they don't mind that being copied around all these years later.&lt;/p&gt;
&lt;p&gt;Anyway, as far as I can remember, this rule never came with a qualifier like "don't share your real name online until you're an adult" or anything like that. Maybe the rule is only for children and I just haven't grown out of it? Like, I was supposed to realize at some point that I no longer have to follow it? No, I'm sure that pseudonymity isn't just for children, or else &lt;a href="https://en.wikipedia.org/wiki/Doxing"&gt;doxxing&lt;/a&gt; wouldn't be considered such an aggression. I'm not the only one who perceives this rule as being generally applicable, and the use of real names as being a shameful symptom of &lt;a href="https://en.wikipedia.org/wiki/Eternal_September"&gt;eternal september&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Internet .1 - Finger protocol - Real Names, everyone pretty much knows each other (or the organization where you work) and is atmosphere is generally very friendly.&lt;/p&gt;
&lt;p&gt;Internet .2 - Finger is no longer used, malicious users and hackers exist, social networks become very personal (Usenet, IRC) pseudonyms make sense, not just for privacy but to usher in a new sentiment of power and respect through anonymity. Computer security is very low.&lt;/p&gt;
&lt;p&gt;Internet .3 - WWW becomes a thing, people still use pseudonyms and generally don't trust anything for good measure, computer security is very low.&lt;/p&gt;
&lt;p&gt;Internet .4 - WWW evolves for the masses/commerce and social networks re-emerge on the web (Myspace, Facebook), people start to use real names everywhere. - A new generation exists that was never on the internet before .4. Computer security is much better.&lt;/p&gt;
&lt;p&gt;Internet .5 - Pseudonyms don't become popular again thanks to the general ignorance of the tube watchers.&lt;/p&gt;
&lt;p&gt;..not entirely accurate..just my 2C&lt;/p&gt;
&lt;p&gt;&lt;a href="https://news.ycombinator.com/item?id=6871394"&gt;wyck&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;When I got on the intertubes, oh-so-many years ago - the rules were simple: the ONLY piece of information you could freely give on public forums, or IRC, was your nick. Now, the only piece of information you are supposed to withhold is your credit card number.&lt;/p&gt;
&lt;p&gt;I'll say this about the new ways: I'm extremely glad that I didn't have my teenage years documented and archived. Dodged that bullet!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://news.ycombinator.com/item?id=6870798"&gt;mrspeaker&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here I go again quoting HN users for general purpose life advice. Probably not the best idea.&lt;/p&gt;
&lt;p&gt;I generally try to approach problems with a long-term perspective. I prefer things that are high-effort, long-lasting, and deliberate. That's one of the reasons I'd rather maintain my own website than play on any company's &lt;a href="/writing/cancel_this_album"&gt;platform&lt;/a&gt;. The word "platform" is used so much these days it makes me sick. They spring up, screw people over for a while, then shut down. I'm not going to entrust them with anything I care about.&lt;/p&gt;
&lt;p&gt;You could say that it's fine to have some real-name presence online: a boring, ordinary drone that blends in with the crowd; and to use pseudonyms to compartmentalize your hobbies, politics, and anything controversial. This is challenging because the more time you spend living in those pseudonyms, the larger a portion of your total life they become, and the more oppressive those compartment walls feel. If you do maintain perfect opsec, and keep your pseudonyms from ever touching, what do you have? A dozen identities, each a shallow &lt;a href="https://en.wikipedia.org/wiki/Magical_objects_in_Harry_Potter#Horcruxes"&gt;dozenth&lt;/a&gt; of a person, and a dozen groups of peers that aren't allowed to mix? I don't think I want to invest such portions of my life into siloed identities, leaving my real name a boring husk of no consequence. I will only ever get to be one meatbag, at the end of the day. We like to treat the internet as if it's a separate place where you live a separate life, but it's not. This is your actual life.&lt;/p&gt;
&lt;p&gt;When I first made my reddit account, I chose the name &lt;a href="https://old.reddit.com/u/GoldenSights"&gt;/u/GoldenSights&lt;/a&gt; instead of &lt;a href="https://old.reddit.com/u/voussoir"&gt;/u/voussoir&lt;/a&gt; because I specifically wanted to use a name that I wasn't using anywhere else. I assumed reddit would be a low-investment site that I would not want to connect back to my identity. But, through reddit's culture of &lt;a href="https://github.com/voussoir/reddit"&gt;bot programming&lt;/a&gt; (&lt;a href="https://en.wikipedia.org/wiki/2023_Reddit_API_controversy"&gt;RIP&lt;/a&gt;) I discovered Python, and now I've been writing Python code for nine years. That's not the kind of time investment I want to keep hidden in a corner. If someone asks me what I've been up to for the past nine years, am I supposed to sheepishly say "uh, nothing, I can't tell you" so that I don't pwn myself? What a waste.&lt;/p&gt;
&lt;p&gt;"Maintain perfect opsec". What kind of CIA espionage LARP am I on here? This sounds stupid. But really, there isn't that much middle ground. Either you allow your online names to lead back to your meatbag self, or you don't.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;I gave up anonymity. I just learned to lean into taking control of my ID. Some time ago, I realized that there's no way for me to participate online, without things being attributed to me.&lt;/p&gt;
&lt;p&gt;I learned this, by setting up a Disqus ID. I wanted to comment on a blog post, and started to set up an account.&lt;/p&gt;
&lt;p&gt;After I started the process, it came back, with a list of random posts, from around the Internet (and some, very old), and said "Are these yours? If so, would you like to associate them with your account?"&lt;/p&gt;
&lt;p&gt;I freaked. Many of them were outright troll comments (I was not always the haloed saint that you see before you) that I had sworn were done anonymously. They came from many different places (including DejaNews). I have no idea how Disqus found them.&lt;/p&gt;
&lt;p&gt;Every single one of them was mine. Many, were ones that I had sworn were dead and buried in a deep grave in the mountains.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://news.ycombinator.com/item?id=31883896"&gt;ChrisMarshallNY&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;In fact, only two or three decades ago it was standard for the telephone service to print everyone's name, address, and phone number in a big book, distribute it to every house and charge you $5 a month extra if you wanted to opt out of the list.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://news.ycombinator.com/item?id=37375010"&gt;13of40&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;In the past I've tracked down the names of people in old photo's with a viable house number by guessing the city and searching newspaper archives. Then confirm with a streetview. The archives will have articles mentioning people with their street address.&lt;/p&gt;
&lt;p&gt;My thought is Americans have become really paranoid compared to people 50 years ago. And people are often under the illusion that people can find out a lot about you with little effort isn't true.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://news.ycombinator.com/item?id=37375229"&gt;Gibbon1&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;p&gt;At the moment, &lt;a href="https://github.com/voussoir"&gt;my github page&lt;/a&gt; already shows my real name. That cat is out. I added that because as a computer science student there is a cultural expectation to use one's github profile as a public portfolio and to include it on resumes. I mean, once again, it would be really weird to say "I've been writing code as a hobby for this many years but I can't show it to you". I guess the pressure for employment was enough for me to break the rule. For a few years I had a linkedin account with a real photo, but I deleted that because linkedin is gross.&lt;/p&gt;
&lt;p&gt;I am usually pretty good at resisting cultural expectations if I want to. I've never tasted alcohol, for example, and I don't swear. I never did use facebook or twitter or instagram or vine or tiktok. And while it'd be more noble to say that I am simply following my own beliefs and methods regardless of expectations, I know that I deliberately go out of my way to be contrarian. To not be a &lt;a href="https://en.wiktionary.org/wiki/normie"&gt;normie&lt;/a&gt;. But although I'm not harboring a secret desire to drink or to watch tiktok, and I am inwardly convinced of these choices, there's no doubt it is also very much an outward thing, a desire to be seen by others as the kind of person who does not follow the norm.&lt;/p&gt;
&lt;p&gt;Sharing your real name and location online is different from following the other kinds of trends, though, because it involves crossing a significant point of no return. If I decide to try a drink, I can then decide to not have another one. I'd never be able to truthfully say I haven't tasted alcohol ever again — it would be a crossing in its own right — but it won't bring me any kind of future harm.&lt;/p&gt;
&lt;p&gt;But once your name and location are public, you can't take it back. There is no return to anonymity. Is that important? Will that be a problem? Will it hurt me? Probably not. 99% chance not. But if it does, you can't go back. As long as you remain anonymous, you have unlimited time to ponder the consequences of deanonymizing; you can always delay that crossing. But once you are deanonymized, no amount of pondering can undo it. Are you sure you've thought it through? How sure?&lt;/p&gt;
&lt;p&gt;The fearmongering that exists, or at least used to exist when I was young, about sharing your identity on the internet is that you'll get harassed by &lt;a href="https://en.wikipedia.org/wiki/Mean_world_syndrome"&gt;stalkers, molesters, and other crazies&lt;/a&gt;. Or, at least, scammers attempting identity theft or fraud or impersonation.&lt;/p&gt;
&lt;p&gt;Wait... that doesn't make sense. Is that what this all comes down to? You've got to be kidding me. It seems like the vast majority of normies make their name, face, birthday, family ties, and whereabouts relatively public on social media, and the vast majority are not victims of death threats or fraud. It's not as rare as being struck by lightning, but if that's the entire scope of the threat then it doesn't seem worth the disproportionate worry about having my name online.&lt;/p&gt;
&lt;p&gt;There are already billions of strangers' names on the internet. To me, I am very important, but to everybody else I am just one more stranger. It seems like knowing someone's home address is a big deal, but if you pan around on a map you've got thousands of people's home addresses right before your eyes. How meaningless. The fact that I write a blog makes me ever so slightly different from the norm, but the stuff I write is not nearly as inflammatory or unhinged as a whole lot of other stuff that's out there, available under the author's real name. I know the names, faces, birthdays, and/or hometowns of various filmmakers, actors, authors, athletes, musicians, CEOs, politicians, professors, and youtubers, and they're doing fine. Why would I not also be fine?&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Horsley's website, created in 1997, listed roughly 200, as the website called them, "abortionists." These abortion providers were listed in three different fonts, as described by the site's legend: "Black font (working); Greyed-out Name (wounded); Strikethrough (fatality)." In addition to this list, the website included the abortion providers' personal information, such as their home addresses, phone numbers, and photographs.&lt;/p&gt;
&lt;p&gt;The website, donned with blood-dripping graphics, both celebrated providers' deaths and, with a wink and a nod, encouraged others to harm the remaining providers on the list so that more names could be crossed off.&lt;/p&gt;
&lt;p&gt;[...]&lt;/p&gt;
&lt;p&gt;This fear is justified. Since 1993, eight abortion providers have been murdered because of their jobs. The most recent assassination was of Dr. George Tiller in 2009, shot while he was serving as an usher in his church. Several of the murders were preceded by Old West–style "wanted" posters that featured providers' personal information, a tactic still in use today.&lt;/p&gt;
&lt;p&gt;&lt;a href="/writing/online_pseudonymity/slate_neal_horsley.html"&gt;Slate - Strikethrough (Fatality)&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ok, so, there really are risks associated with deanonymizing. There are also risks associated with stepping outside and eating solid foods. I don't expect to be molested and murdered over my work, but it's... possible. While I was proofreading this article, Maddox &lt;a href="https://www.youtube.com/watch?v=tgDIZ17440o" title="Is Justin Whang a plagiarist and a hack, or just a plagiarist?"&gt;released&lt;/a&gt; an hour long tale of stalking and harassment, and I guess I'll just keep telling myself it wouldn't happen to me because I'm not as inflammatory as he is. Or maybe I just shouldn't risk it. I have two choices: one carries a small chance of harm and the other carries none; so even if I can't actually justify the thought process, I may as well stay on the safe side.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Bang!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;One of my assumptions has just burst. The assumption that remaining pseudonymous forever is not harmful. I am very lonely. Like, &lt;a href="/writing/e_unibus_pluram_1993"&gt;Joe Briefcase&lt;/a&gt; lonely. A lifetime of compartmentalization and self-imposed isolation between my real life and hobbies and work and creations and thoughts is taking its toll. My refusal to share my digital pseudonymous work with real-life coworkers / friends / acquaintances, and my refusal to make myself available for discovery / contact online is successfully preventing me from meeting like-minded people. There's more to it than that, but this isn't helping. I'm pretty certain that the &lt;a href="https://en.wikipedia.org/wiki/Expected_value"&gt;expected value&lt;/a&gt; of this compartmentalized lifestyle is worse than a more public one.&lt;/p&gt;
&lt;p&gt;Another reason to be more public is that if you don't define the web search results that come up for your name, you could be giving that privilege to someone else. If you ever do acquire some kind of enemy, and they publish your name and face with some message of hate, and they get to dominate the search results about you because you've kept all the good things about yourself a secret, that would kind of suck. Is this too contrived? Maybe.&lt;/p&gt;
&lt;center&gt;&lt;a href="https://www.youtube.com/watch?v=sXE8LdXzeHM" style="color: currentcolor; text-decoration: none;"&gt;* * *&lt;/a&gt;&lt;/center&gt;&lt;p&gt;This problem is on my mind because recently I have been attending some free outdoor concerts put on by the city as part of my effort to get out of the house more and curb my loneliness. I always bring my camera and take pictures and email them to the band as a way of giving something back. Plus it is useful and fun practice for me.&lt;/p&gt;
&lt;p&gt;The reason this is a problem is because some of the pictures I've taken are actually really good, if you'll excuse the self-praise. I've got quite a few photos now that I think are pretty special, but you wouldn't know it because my &lt;a href="https://voussoir.net/photography"&gt;/photography&lt;/a&gt; page is stagnating under the self-imposed pressure to not reveal who or where I am. In my &lt;a href="/writing/hobby_photography"&gt;hobby photography article&lt;/a&gt;, I already wrote that I'm not interested in taking meaningless eye candy pictures. Well, it turns out that meaningful pictures usually involve some quantity of recognizable people and places, at least for the genre and manner of photography I want to do.&lt;/p&gt;
&lt;p&gt;So far, the responses I've gotten from the bands has been (presumably genuine) ecstatic appreciation. One of the guys I took pictures of last week was in the audience this week, and he and his wife approached me to say how grateful they were. As I said in &lt;a href="/writing/motivations_for_writing"&gt;my motivations for writing&lt;/a&gt;, visual arts can wow people in a way that my other hobby, computer programming, cannot. People actually &lt;em&gt;want&lt;/em&gt; to share my photos on their website, and they &lt;em&gt;want&lt;/em&gt; to credit me. "You can post the pictures but I prefer not to be credited" is so odd. Who do I think I am, some kind of Batman? Here I am sending an email to a musician whose email address I got from their website with their name and hometown and upcoming show dates, all public, and I'm playing Batman. My logic is completely incoherent. If I'm afraid of my work being known, maybe I don't deserve to do it in the first place.&lt;/p&gt;
&lt;p&gt;Now that it's been a few weeks, I'm starting to learn the names of some of the also-regulars at these concerts. I've run into neighbors, coworkers, and even my dentist. Apparently one of the also-regulars goes to the same dentist. Is this what having a &lt;a href="https://www.youtube.com/watch?v=VvdQ381K5xg" title="Not Just Bikes - The Great Places Erased by Suburbia (the Third Place)"&gt;third place&lt;/a&gt; is like? People are beginning to ask questions:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Them:&lt;br /&gt;
Are you shooting for a newspaper?&lt;/p&gt;
&lt;p&gt;Me:&lt;br /&gt;
No, just for fun.&lt;/p&gt;
&lt;p&gt;Them:&lt;br /&gt;
Oh, what's your instagram?&lt;/p&gt;
&lt;p&gt;Me:&lt;br /&gt;
Sorry, I don't do instagram.&lt;/p&gt;
&lt;p&gt;Them:&lt;br /&gt;
Do you have a website where I can see your work?&lt;/p&gt;
&lt;p&gt;Me:&lt;br /&gt;
Ummmm... I was just gonna email the band directly.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I don't give them a link to my website, partly because I don't know if I want to build connections between voussoir.net and my real identity, and partly because I would look like a big idiot with my 'portfolio' full of anonymous closeups and abstracts, with none of the live band photos which I just told them I've been taking for the past four weeks. So instead I just look like a different kind of big idiot who takes five hundred pictures and apparently does almost nothing with them and awkwardly terminates the conversation at any mention of an outlet. Gotta go, bye!&lt;/p&gt;
&lt;p&gt;I am not interested in making a new pseudonym and buying a new domain name for this purpose, either. Once again, I don't want to put years of investment into another identity that isn't me, leaving my actual identity empty and uninvested. Besides, all it takes is one person to share that domain in a facebook comment with "thanks to [my name] for taking pictures at our event" and it's blown anyway. May as well be ahead of it.&lt;/p&gt;
&lt;p&gt;So, my real name is already tied to my website. That line has been crossed. But to publish these pictures would be to cross another line: tying my identity to a city. An environment for which I have already &lt;a href="/writing/not_just_bikes"&gt;expressed&lt;/a&gt; some &lt;a href="https://www.youtube.com/watch?v=xN4Ev8AfV3E" title="YTSPPOVVLOG #49: Street Photography in Suburbia, California"&gt;disdain&lt;/a&gt;. With named buildings in the background and the band's name on their banner or drum set, you could find exactly where I was... and where I'll be next week. That's pretty normal, but I don't like it. My other option is to let this body of work stagnate.&lt;/p&gt;
&lt;p&gt;I've been using the word "portfolio", but I'll reiterate that this is absolutely not some kind of side-hustle for me. I want to publish these pictures on my website not to generate leads, but because I'm proud of them. I publish all of my Python code for fun, and I want to publish my pictures for fun, too. Also, because when I'm standing face-to-face with someone asking "Can I see more of your work?", I want to be able to say "Yes", instead of "No".&lt;/p&gt;
&lt;p&gt;You're overthinking it!&lt;/p&gt;
&lt;p&gt;Feedback wanted.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: I have not received any feedback, and I've taken some more good pictures. &lt;a href="https://voussoir.net/photography"&gt;Scales tipped&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/online_pseudonymity/photo1.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/online_pseudonymity/photo2.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/online_pseudonymity/photo3.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/online_pseudonymity/photo4.jpg" /&gt;&lt;/p&gt;
&lt;!-- By the way, here is a tip for any photographers or photographer-hopefuls if you're feeling discouraged about the quality of your work compared to pictures you see online: stay away from photography forums, at least for a little while, and go deal with regular people instead. When you take pictures of a small local band (or dance group or festival or what have you), you might very well be the first person to ever do so. If you share those pictures on a photography forum, you'll get a bunch of tryhards comparing your work to every band photo available on the world wide web; if you share them with the band themselves, they'll say "we've never gotten anything like this before, thank you, what a gift!". It's pretty obvious which opinion is worth more. Your work doesn't have to be the best in the world, because the rest of the world doesn't matter to that one group. --&gt;

&lt;hr /&gt;
&lt;p&gt;&lt;a href="https://git.voussoir.net/voussoir/voussoir.net/commits/master/voussoir.net/writing/online_pseudonymity/online_pseudonymity.md"&gt;View this document's history&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://git.voussoir.net/voussoir/voussoir.net/commit/89c431a15ac1df93751f56f915c63691f0dda366"&gt;&lt;time datetime="2023-09-12T19:43:52-07:00"&gt;2023-09-12&lt;/time&gt; Scales tipped.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://git.voussoir.net/voussoir/voussoir.net/commit/798f55e0e5a2d379fe8544687799574f6220eb2d"&gt;&lt;time datetime="2023-09-04T11:12:58-07:00"&gt;2023-09-04&lt;/time&gt; Add phonebook comment.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://git.voussoir.net/voussoir/voussoir.net/commit/e84cc63a5332c8fdd0279761ced2a9a085f603f5"&gt;&lt;time datetime="2023-08-30T21:32:12-07:00"&gt;2023-08-30&lt;/time&gt; Add online_pseudonymity.md.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;address&gt;Contact me: writing@voussoir.net&lt;/address&gt;
&lt;p&gt;If you would like to subscribe for more, add this to your RSS reader: &lt;a href="/writing/writing.atom" rel="alternate" type="application/atom+xml"&gt;https://voussoir.net/writing/writing.atom&lt;/a&gt;&lt;/p&gt;
&lt;/article&gt;</description><author>voussoir.net/writing</author><pubDate>Thu, 31 Aug 2023 07:32:12 GMT</pubDate><guid isPermaLink="true">https://voussoir.net/writing/online_pseudonymity</guid></item><item><title>React Three Fiber for Displaying a glTF 3D Model</title><link>https://www.tderflinger.com/en/react-three-fiber-displaying-gltf-model</link><description>React Three Fiber is a React wrapper for the Three.js library, facilitating the integration of 3D objects into React applications. It claims similar speed and feature capabilities as Three.js directly. In this article, I show you by using a demo application how to display a glTF model in the browser.</description><author>Thoughts by Thomas Derflinger</author><pubDate>Thu, 31 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.tderflinger.com/en/react-three-fiber-displaying-gltf-model</guid></item><item><title>The Microwave With Feature Creep</title><link>https://backendhance.com/blog/2023/the-microwave-with-feature-creep/</link><description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;p&gt;&lt;a href="https://backendhance.com/blog/2023/what-sound-does-your-microwave/"&gt;Meine Mikrowelle war kaputt.&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;Also: Eine neue muss her.&lt;/p&gt;
&lt;p&gt;Ich habe hier aber einen kleinen Tick. Das betrifft alles unvergängliche das ich kaufe. Ich will einen Fehlkauf vermeiden. &lt;strong&gt;Lieber einmal mehr Geld ausgeben, als in zwei Jahren wieder neu kaufen.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Hier muss ich aufpassen nicht in einer Rabbit-Hole zu landen. Ich kann Stunden in das Lesen von Reviews versenken.&lt;/p&gt;
&lt;p&gt;Aber was will ich überhaupt vergleichen?&lt;/p&gt;
&lt;h2 id="als-erstes-kommt-die-anforderungsanalyse"&gt;
&lt;a class="anchor" href="#als-erstes-kommt-die-anforderungsanalyse"&gt;Als erstes Kommt die Anforderungsanalyse&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Bevor ich überhaupt zwischen Modellen vergleichen kann, muss ich mir klar werden was ich eigentlich brauche.&lt;/p&gt;</description><author>Backendhance</author><pubDate>Thu, 31 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/the-microwave-with-feature-creep/</guid></item><item><title>Heist: Haskell Templating Woes</title><link>https://fbrs.io/heist-2/</link><description>&lt;p&gt;Yesterday I got my feet wet by rendering a single, measly splice. And at first it didn’t look like I’d even accomplish that before the end of the day. Today I want to extend the example by adding more splices that operate on different data. In the &lt;a href="http://snapframework.com/docs/tutorials/compiled-splices"&gt;compiled splices tutorial&lt;/a&gt; they have an example that shows how to render a list of persons. But in a real world application with dozens of routes you will have dozens of splices that all require different data. But if all of those compiled splices end up in your Heist state under a single type, would that type end up being the concatentation of all the parameters of all splices? Let’s find out.&lt;/p&gt;
&lt;p&gt;The goal for today is this: Make a (fake) database call and use the data to generate two splices, one for a &lt;code&gt;Text&lt;/code&gt; and one for an &lt;code&gt;Int&lt;/code&gt;, that are then used in a template.&lt;/p&gt;
&lt;p&gt;I specifically do not want to create some application monad and access it from within splices. My major concern with this is that individual splices are now free to make database calls, much like a GraphQL API where each field is backed by an independent resolver, which can make as many database calls as it wants. I’m actually not a fan of this application monad pattern, because it makes it all too easy to have code access your logger or your database that really shouldn’t.&lt;/p&gt;
&lt;p&gt;Here’s how I imagine this will work in pseudo-code:&lt;/p&gt;
&lt;pre class="language-haskell "&gt;&lt;code class="language-haskell"&gt;main = do
  data &amp;lt;- fakeDatabaseCall
  heistState &amp;lt;- initHeist { ... }

  let spliceA = genSpliceA (data.someNumber)
  let spliceB = genSpliceB (data.someText)

  heistState.splices = heistState.splices
    &amp;#x2f;&amp;#x2f; { spliceA = spliceA, spliceB = spliceB }

  renderTemplate heistState &amp;quot;foo&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I suspect that merging splices into the Heist state will be a major undertaking. I have no idea how I can modify Heist state. Indeed, the documentation for &lt;code&gt;initHeist&lt;/code&gt; says this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We don’t provide functions to add either type of loadtime splices to your HeistState after initHeist because it doesn’t make any sense unless you re-initialize all templates with the new splices.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The documentation does mention&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Heist’s HeistState -&amp;gt; HeistState “filter” functions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;but I don’t know where they are. They do have a few functions that all work in the &lt;code&gt;HeistT n m ()&lt;/code&gt; environment, but I don’t know how I would use them. I could call &lt;code&gt;modifyHS&lt;/code&gt; in a splice function, but then I’m once again in a load-time splice function and I want to avoid the whole runtime stuff-everything-into-an-application monad.&lt;/p&gt;
&lt;p&gt;So I guess modifying the Heist state is not an option after all. What else can I do then, that’s not the giant-all-encompassing-application-monad-of-doom? The tutorial code has this snippet in it:&lt;/p&gt;
&lt;pre class="language-haskell "&gt;&lt;code class="language-haskell"&gt;hs &amp;lt;- load baseDir (&amp;quot;people&amp;quot; ## allPeopleSplice)
let runtime = fromJust $ C.renderTemplate hs &amp;quot;people&amp;quot;
builder &amp;lt;- evalStateT (fst runtime)
            [ Person &amp;quot;John&amp;quot; &amp;quot;Doe&amp;quot; 42
            , Person &amp;quot;Jane&amp;quot; &amp;quot;Smith&amp;quot; 21
            ]
return $ toByteString builder
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice that they’re supplying the data for this view through a simple, hard coded state monad. I could create runtime splices that use a bespoke reader monad and then in my route handlers fill that reader monad with all the data for that route. I suspect that this will hamper re-use though. If you have a splice that needs an int and its used in two different templates that use a different reader monad each, then I can think of two options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;duplicate the splice&lt;/li&gt;
&lt;li&gt;make the splice a bit more generic with a type class and then implement that type class for each monad&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-haskell "&gt;&lt;code class="language-haskell"&gt;class HasNewsCount a where
  getNewsCount :: a -&amp;gt; Int

instance HasNewsCount ViewA where
  getNewsCount = undefined

instance HasNewsCount ViewB where
  getNewsCount = undefined
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anyway, let’s give this a try. First, I need a somewhat more realistic example. I created two views for two routes:&lt;/p&gt;
&lt;pre class="language-xml "&gt;&lt;code class="language-xml"&gt;&amp;#x2f;&amp;#x2f; view_a.tpl
&amp;lt;apply template=&amp;quot;index&amp;quot;&amp;gt;
  &amp;lt;person &amp;#x2f;&amp;gt;
  &amp;lt;foo &amp;#x2f;&amp;gt;
&amp;lt;&amp;#x2f;apply&amp;gt;

&amp;#x2f;&amp;#x2f; view_b.tpl
&amp;lt;apply template=&amp;quot;index&amp;quot;&amp;gt;
  &amp;lt;count &amp;#x2f;&amp;gt;
  &amp;lt;foo &amp;#x2f;&amp;gt;
&amp;lt;&amp;#x2f;apply&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;They both have &lt;code&gt;&amp;lt;foo /&amp;gt;&lt;/code&gt; in common, so that I can go through the use case of having a shared splice that gets its data from different reader monads (unless I figure out a solution that doesn’t need those monads, but I doubt it). They also each have a splice that’s exclusive to the view, so that the data for each view as a whole is different.&lt;/p&gt;
&lt;p&gt;I cleaned up &lt;code&gt;main.hs&lt;/code&gt; which now looks like this:&lt;/p&gt;
&lt;pre class="language-haskell "&gt;&lt;code class="language-haskell"&gt;mainSplices :: Monad m =&amp;gt; Splices (C.Splice m)
mainSplices = return mempty

main :: IO ()
main = do
  let spliceConfig =
        mempty
          &amp;amp; scLoadTimeSplices .~ defaultLoadTimeSplices
          &amp;amp; scTemplateLocations .~ [loadTemplates &amp;quot;app&amp;quot;]

  eitherHeistState &amp;lt;-
    initHeist $
      emptyHeistConfig
        &amp;amp; hcNamespace .~ &amp;quot;&amp;quot;
        &amp;amp; hcErrorNotBound .~ False
        &amp;amp; hcSpliceConfig .~ spliceConfig
        &amp;amp; hcCompiledSplices .~ mainSplices

  case eitherHeistState of
    Left err -&amp;gt;
      putStrLn $ &amp;quot;Heist init failed: &amp;quot; ++ show err
    Right heistState -&amp;gt; do
      case C.renderTemplate heistState &amp;quot;view_a&amp;quot; of
        Nothing -&amp;gt; do
          putStrLn &amp;quot;Index not found!&amp;quot;
        Just (docRuntime, _) -&amp;gt; do
          docBuilder &amp;lt;- docRuntime
          print $ toByteString docBuilder
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I’m already scared of this &lt;code&gt;Monad m =&amp;gt; Splices (C.Splice m)&lt;/code&gt; being my downfall. This &lt;code&gt;m&lt;/code&gt; will have to be specialized to an appropriate reader monad for each view. But without having it be the amalgamation of all views (or their data).&lt;/p&gt;
&lt;p&gt;And just as I thought, this doesn’t work. In retrospect it’s rather obvious.&lt;/p&gt;
&lt;pre class="language-haskell "&gt;&lt;code class="language-haskell"&gt;fooSplice :: (MonadIO m, MonadReader e m, HasFoo e) =&amp;gt; C.Splice m
fooSplice = do
  return $ C.yieldRuntimeText $ do
    fooValue &amp;lt;- lift $ asks foo
    return $ T.pack $ show fooValue
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you build a few splices like that, all with a different &lt;code&gt;Has*&lt;/code&gt; constraint, the top level splices list must gather up all those constraints.&lt;/p&gt;
&lt;p&gt;Recapping:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;modifying the list of splices after &lt;code&gt;initHeist&lt;/code&gt; seems hard and not what you’re supposed to do&lt;/li&gt;
&lt;li&gt;any concrete type in a splice will bubble up to the top level splices definition, where you’ll be forced to have a type that is the concatenation of all child types&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I took another look at the &lt;a href="https://stackoverflow.com/questions/8023191/using-values-not-from-the-application-monad-with-heist-templates"&gt;Stack Overflow answer&lt;/a&gt; here and specifically looked at the source code mentioned in a comment. But that source code seems to reference a Heist version that is practically ancient at this point. I don’t think the option mentioned by “mightybyte” is viable anymore.&lt;/p&gt;
&lt;p&gt;I updated the example repository at &lt;a href="https://github.com/cideM/heist_getting_started/tree/498e072a9aab8f06b1d7a3f4601e5062198e1b57"&gt;this commit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Next I’ll have to figure out which other templating library I can use or if I want to just drop Haskell for this project.&lt;/p&gt;</description><author/><pubDate>Thu, 31 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://fbrs.io/heist-2/</guid></item><item><title>Elum</title><link>https://kajic.com/post/727110120173142016</link><description>&lt;p&gt;Wisp bundled in blue sky&lt;/p&gt;&lt;p&gt;Yellow leaves falling &lt;/p&gt;&lt;p&gt;A bridge over scents of Florence  &lt;/p&gt;&lt;p&gt;Program think in silence &lt;/p&gt;</description><author>ka-yich</author><pubDate>Wed, 30 Aug 2023 23:10:46 GMT</pubDate><guid isPermaLink="true">https://kajic.com/post/727110120173142016</guid></item><item><title>Becoming a startup millionaire</title><link>https://anyfactor.github.io/posts/becoming_startup_millionaire/</link><description>&lt;p&gt;The successful exit startup rulebook. Creating a startup based on a feature of a big software.&lt;/p&gt;</description><author>Anyfactor's blog</author><pubDate>Wed, 30 Aug 2023 22:54:07 GMT</pubDate><guid isPermaLink="true">https://anyfactor.github.io/posts/becoming_startup_millionaire/</guid></item><item><title>Exploring Practical Design Patterns: Connecting Theory to the Real World</title><link>https://ankitag9.substack.com/p/exploring-practical-design-patterns</link><description>Introduction</description><author>Perspective by Ankit Agarwal</author><pubDate>Wed, 30 Aug 2023 10:53:06 GMT</pubDate><guid isPermaLink="true">https://ankitag9.substack.com/p/exploring-practical-design-patterns</guid></item><item><title>Terraform vs. GitOps vs. System Initiative</title><link>https://justingarrison.com/blog/2023-08-29-terraform-gitops-system-initiative/</link><description>What are key differences between infrastructure management options in 2023</description><author>Justin Garrison's Homepage</author><pubDate>Wed, 30 Aug 2023 08:01:58 GMT</pubDate><guid isPermaLink="true">https://justingarrison.com/blog/2023-08-29-terraform-gitops-system-initiative/</guid></item><item><title>How I use Django's messages framework</title><link>https://brntn.me/blog/how-i-use-djangos-messages-framework/</link><description>&lt;!-- How I use Django's messages framework  --&gt;

&lt;p&gt;&lt;img alt="Screenshot of four messages displayed on a webpage" src="https://media.brntn.me/postie/2d83f82f.png" /&gt;&lt;/p&gt;
&lt;p&gt;This is just a quick post to share a few code snippets that I find useful when dealing with Django's &lt;a href="https://docs.djangoproject.com/en/dev/ref/contrib/messages/"&gt;messages framework&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Have you noticed that when you start a new Django project &lt;code&gt;django.contrib.messages&lt;/code&gt; is included in your INSTALLED_APPS?
This is Django's lightweight framework for showing one-time notifications to users.&lt;/p&gt;
&lt;p&gt;&amp;quot;One-time&amp;quot; describes one off the messages framework's key features: once a message has been displayed to the user it will be dismissed and not displayed again.
I use this all the time to display success and error messages to users in simple Django applications.&lt;/p&gt;
&lt;p&gt;Out of the box, messages supports the five standard levels of criticality (similar to Python's logging framework): DEBUG, INFO, SUCCESS, WARNING, ERROR.
The &lt;a href="https://docs.djangoproject.com/en/dev/ref/settings/#std-setting-MESSAGE_LEVEL"&gt;&lt;code&gt;MESSAGE_LEVEL&lt;/code&gt;&lt;/a&gt; setting is used to specify the minimum level to display – by default Django will show INFO and above.&lt;/p&gt;
&lt;p&gt;When processing a request you can add a message with a snippet like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;from django.contrib import messages

messages.add_message(request, messages.SUCCESS, &amp;quot;Rating created successfully.&amp;quot;)
&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The required middleware (&lt;code&gt;SessionMiddleware&lt;/code&gt;) and template context processor (&lt;code&gt;django.contrib.messages.context_processors.messages&lt;/code&gt;) are also installed by default.
This means that a &lt;code&gt;messages&lt;/code&gt; variable it available in your template to access this message.&lt;/p&gt;
&lt;p&gt;I insert this HTML snippet into by base template where I want messages to be shown:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;{% if messages %}
  &amp;lt;ul class=&amp;quot;messages&amp;quot;&amp;gt;
    {% for message in messages %}
    &amp;lt;li{% if message.tags %} class=&amp;quot;{{ message.tags }}&amp;quot;{% endif %}&amp;gt;{{ message }}&amp;lt;/li&amp;gt;
    {% endfor %}
  &amp;lt;/ul&amp;gt;
{% endif %}
&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It uses a basic semantic structure to output the messages.
Turns out that even though I've been copying this snippet from project to project for the last few years, this is &lt;a href="https://docs.djangoproject.com/en/dev/ref/contrib/messages/#displaying-messages"&gt;straight out of the documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;CSS classes will be used to change the style of the different message types.
The colours below are from the &lt;a href="https://yeun.github.io/open-color/"&gt;open-color&lt;/a&gt; project.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;.messages {
    list-style: none;
    margin: 1em 0 2em;
    padding: 0;
}

.messages li {
    padding: 1em;
    width: 100%;
    line-height: 2.0em;
}

.messages li.info, .messages li.debug {
    background-color: #d0ebff;
}

.messages li.success {
    background-color: #b2f2bb;
}

.message li.warning {
    background-color: #ffec99;
}

.messages li.error {
    background-color: #ffe3e3;
}
&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Combining these two snippets gives me a quick and easy way to give success and failure notifications to users.
The CSS provides some basic styling that can be extended on a per-project basis with some additional global styles.&lt;/p&gt;</description><author>Brenton Cleeland</author><pubDate>Wed, 30 Aug 2023 03:30:04 GMT</pubDate><guid isPermaLink="true">https://brntn.me/blog/how-i-use-djangos-messages-framework/</guid></item><item><title>Absurd Success</title><link>https://www.marginalia.nu/log/87_absurd_success/</link><description>&lt;p&gt;So&amp;hellip; I&amp;rsquo;ve had the most unreal week of coding. Zero exaggeration, I&amp;rsquo;ve halved the
RAM requirements of the search engine, removed the need to take the system
offline during an upgrade, removed hard limits on how many documents can be indexed,
and quadrupled soft limits on how many keywords can be in the corpus.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s been a long term goal to keep it possible to run and operate the system
on low-powered hardware, and so far improvements have been made, to the point
where my 32 Gb RAM developer machine feels spacey rather than cramped, but this
set of changes takes it several notches further.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Wed, 30 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/87_absurd_success/</guid></item><item><title>How to buy a car</title><link>/how-to-buy-car/</link><description>&lt;p&gt;Recently, I purchased a new-to-me (used) car. Unfortunately, after I purchased it there was something wrong with it. It wasn't a costly repair but annoying nonetheless. And there are a lot of horror stories of buying used cars. So, I decided to put together a checklist (for myself mostly) that I can run through whenever buying a car (or helping someone else to).&lt;/p&gt;
&lt;h2&gt;The list&lt;/h2&gt;
&lt;h3&gt;Before you view the car&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[] Check the MOT History - ask the seller if the minor defects have been addressed&lt;/li&gt;
&lt;li&gt;[] Review common faults with the car for the mileage - ask the seller if they have been addressed&lt;/li&gt;
&lt;li&gt;[] Check if it has a service history and who with (dealer service history is better) - I wouldn't recommend buying a car without a service history.&lt;/li&gt;
&lt;li&gt;[] Get an insurance quote on the car - can you afford it?&lt;/li&gt;
&lt;li&gt;[] Check the vehicle tax&lt;/li&gt;
&lt;li&gt;[] Do a HPI check on the car&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;When viewing the car&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[] Check the tyre tread depth - if it's low, you can factor in the cost for new tyres as a negotiation point&lt;/li&gt;
&lt;li&gt;[] Check the service history paper work and the electronic service history (if available).&lt;/li&gt;
&lt;li&gt;[] Check all the interior functions work - AC, Radio, Bluetooth, Steering wheel controls, wipers, windscreen washer, all lights.&lt;/li&gt;
&lt;li&gt;[] Check the boot to make sure the spare tyre/repair kit is there.&lt;/li&gt;
&lt;li&gt;[] Check the engine bay for any leaks or damage.&lt;/li&gt;
&lt;li&gt;[] Check fluid levels in the engine bay - coolant, oil, brake fluid, power steering fluid etc.&lt;/li&gt;
&lt;li&gt;[] Make sure the oil cap isn't milky - this could indicate a head gasket failure.&lt;/li&gt;
&lt;li&gt;[] Check the bodywork for any damage.&lt;/li&gt;
&lt;li&gt;[] Turn the wheel all the way to the left and right to check for any clicking noises.&lt;/li&gt;
&lt;li&gt;[] Turn the steering wheel each way and check the tie rods and ball joints&lt;/li&gt;
&lt;li&gt;[] Make sure both keys work&lt;/li&gt;
&lt;li&gt;[] Give the car a test drive - make sure it can rev high, no warning lights appear and the breaks are responsive. If possible, make sure that the parking break holds the car on a hill.&lt;/li&gt;
&lt;li&gt;[] After test driving leave the car idle, make sure the idle sounds smooth and underneath the car there are no leaks.&lt;/li&gt;
&lt;li&gt;[] Check the VIN of the car matches the V5C&lt;/li&gt;
&lt;li&gt;[] Examine how clean the car is inside and the condition of the interior, usually this is a good sign of the cars care.&lt;/li&gt;
&lt;li&gt;[] Examine the brand of tyres the car has, if they are budget tyres and/or don't all match then this may be a sign the car has been maintained on a tight budget.&lt;/li&gt;
&lt;/ul&gt;</description><author/><pubDate>Wed, 30 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">/how-to-buy-car/</guid></item><item><title>Business Version 2</title><link>/consulting-launch-2023/</link><description>&lt;p&gt;I have been working as a software engineer for 8 years. Over the past 2 years, I've been freelancing for startups, enterprises and everything in between.&lt;/p&gt;
&lt;p&gt;The next stage of my business is pivoting away from pure freelancing work to a more complete offering of products and services.&lt;/p&gt;
&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;I launched a business 24 months ago chiefly to work as a freelancer. I'm now looking to diversify and offer a broader suite of products and services to businesses.
These include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;🧙‍♂️ Cloud architecture and optimisation&lt;/li&gt;
&lt;li&gt;⚙️ Hands-software development&lt;/li&gt;
&lt;li&gt;🌅 Engineering leadership, management and training&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want to work with me, please drop me an email at &lt;code&gt;me at joshghent.com&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;What I (now) offer&lt;/h2&gt;
&lt;p&gt;The key to any successful business is bringing the maximum amount of value to customers. This one is, thankfully, no different. I've chosen 3 key areas where I believe my skills and experience can make a difference.&lt;/p&gt;
&lt;h3&gt;🧙‍♂️ Cloud architecture and optimisation&lt;/h3&gt;
&lt;p&gt;Having worked as a software engineer for over 8 years, I've come across every type of cloud architecture imaginable. I've successfully transformed monolithic applications into microservices, migrated applications from on-premise to the cloud and optimised cloud infrastructure to save money.&lt;/p&gt;
&lt;p&gt;When I work with any organisation, I focus on the following four pillars (in priority order):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Observability.&lt;/strong&gt; Helping organisations to get a handle on the numbers behind their products and developers to gain insight into their code. Having worked with a number of different observability tools, I can help organisations to choose the right one for them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reliability.&lt;/strong&gt; The only thing worse than an app that is slow is an app that is down. I help organisations to design, build and ship robust products that can scale as the business grows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance.&lt;/strong&gt; Application speed is a feature and competitive advantage. Taking a holistic view, I can provide indepth insights into your applications performance and work with your team to improve it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cost.&lt;/strong&gt; No one wants an AWS bill that leaves you sobbing uncontrollably in a corner. I can help bring confidence and restore consistency to your cloud spend. I approach this by looking at the other three pillars and optimising them to reduce costs.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Generally, my approach is simple, I work with you to understand your business and goals. And then I build you a plan to achieve them by means of the technology you're building.
I don't recommend a one-size-fits-all approach or that you need to hire a giant devops team to manage your infrastructure.
I believe that the best solutions are the simplest ones and I work with you to find the right solution for your business.&lt;/p&gt;
&lt;h3&gt;⚙️ Hands-software development&lt;/h3&gt;
&lt;p&gt;I've spent the past 8 years working directly with code. My areas of expertise are back-end development (particularly API's and event-driven microservices) and infrastructure (particularly serverless). And I've extensively worked with both greenfield and legacy projects.&lt;/p&gt;
&lt;p&gt;I've worked with lots of different languages and frameworks, but generally favour these stacks:&lt;/p&gt;
&lt;h4&gt;Stack 1&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Frontend&lt;/em&gt;: React/Next.js, TailwindCSS, TypeScript&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Backend&lt;/em&gt;: Node.JS, Typescipt&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Datastore&lt;/em&gt;: DynamoDB, S3, SQS&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Infrastructure&lt;/em&gt;: AWS, SST, Cloudflare Pages&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Stack 2&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Frontend&lt;/em&gt;: Laravel&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Backend&lt;/em&gt;: PHP/Laravel&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Datastore&lt;/em&gt;: PostgreSQL, Redis&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Infrastructure&lt;/em&gt;: Render.com, Cloudflare&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The skills listed above are not exhaustive. I'm always happy picking up new technologies and languages as required.&lt;/p&gt;
&lt;p&gt;Software development goes beyond just coding. I take pride in producing robust code, complemented by concise documentation, and prioritize clear communication with both technical and non-technical individuals.&lt;/p&gt;
&lt;h3&gt;🌅 Engineering leadership, management and training&lt;/h3&gt;
&lt;p&gt;Encompassed within many of my roles is leadership and training. I believe I can help your organisation to train, and retain your engineers - and build a thriving engineering culture in the process. To do this, my approach focuses on&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Creating &lt;em&gt;systems&lt;/em&gt; that empower the team to drive their own growth.&lt;/li&gt;
&lt;li&gt;Introducing &lt;em&gt;fair and accurate metrics&lt;/em&gt; to motivate the team and measure progress.&lt;/li&gt;
&lt;li&gt;Building a culture of sharing and learning by introducing &lt;em&gt;knowledge shares&lt;/em&gt;, &lt;em&gt;pair programming&lt;/em&gt; and &lt;em&gt;code reviews&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Establishing &lt;em&gt;psychological safety&lt;/em&gt; by means of &lt;em&gt;blameless postmortems&lt;/em&gt;, &lt;em&gt;retrospectives&lt;/em&gt; and &lt;em&gt;one-to-ones&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Making sure that communication between stakeholders and the team is &lt;em&gt;transparent&lt;/em&gt; and &lt;em&gt;effective&lt;/em&gt;, with shared goals and values.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have primarily worked with engineering teams who have a headcount of 1-20 people and successfully introduced these practices to them.&lt;/p&gt;
&lt;h3&gt;📟 Fixed cost services&lt;/h3&gt;
&lt;p&gt;These fixed price services are designed to give your team a boost in a particular area. They are designed to be delivered in a short time frame and provide you with a clear plan of action to move forward.&lt;/p&gt;
&lt;p&gt;If you would like more information about the services below please get in touch.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cloud Architecture Plan or Review - £1,300&lt;/li&gt;
&lt;li&gt;Website/Application Performance Audit - £1,500&lt;/li&gt;
&lt;li&gt;Observability Audit - £750&lt;/li&gt;
&lt;li&gt;Team Strategy Documentation - £2,500&lt;/li&gt;
&lt;li&gt;Team Training - £1,250&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All prices are exclusive of VAT.&lt;/p&gt;
&lt;h3&gt;🎁 Products&lt;/h3&gt;
&lt;p&gt;In addition to the services above, I'm also working on a number of products that I have built as a result of problems observed in the organisations I have worked with. These include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LoginLlama&lt;/strong&gt; - A suspicious login detection service for your applications.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These products can be provided to your organisation for a discounted rate and implemented by myself.&lt;/p&gt;
&lt;h2&gt;Work with me&lt;/h2&gt;
&lt;p&gt;If you're interested in the above, and want to work together, please drop me an email at &lt;code&gt;me at joshghent.com&lt;/code&gt;. Or book a call with me &lt;a href="https://calendly.com/joshghent/consultation"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I look forward to hearing from you!&lt;/p&gt;</description><author/><pubDate>Wed, 30 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">/consulting-launch-2023/</guid></item><item><title>Can You Customize my Startup's Login Page?</title><link>https://ph-uhl.com/can-you-customize-my-startup-s-login-page/</link><description>&lt;p&gt;&lt;strong&gt;Early startup founder:&lt;/strong&gt; Can you customize that login page for me?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Me:&lt;/strong&gt; Sure I can, but it doesn't matter! We shouldn't bother right now.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Early startup founder:&lt;/strong&gt; But it needs to look good, so more people will
sign up. And we need to...</description><author>Philipp Uhl</author><pubDate>Wed, 30 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ph-uhl.com/can-you-customize-my-startup-s-login-page/</guid></item><item><title>The Lazy Garbage Nursery Guide</title><link>https://taylor.town/lazy-nursery</link><description>If you have waste and space, you're wasting space.</description><author>taylor.town</author><pubDate>Wed, 30 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/lazy-nursery</guid></item><item><title>Ozymandias is 35% off for Steam Strategy Fest!</title><link>/blog/2023/08/30/ozymandias-in-steam-strategy-fest.html</link><description>Just a quick one today, but if you weren’t already aware, this week is Steam’s “Strategy Fest” &amp;amp; Ozymandias is taking part! To coincide with the fest, we’ve also released the sixth &amp;amp; final season pass update which includes the new “Andes” map, where four ancient Amazonian tribes are pitted in a race to acquire precious food before being cut off by their rivals: It’s hard to believe that the game has been out for almost a year now, and in all honesty it has performed far better than I’d imagined…</description><author>biggles.games</author><pubDate>Wed, 30 Aug 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">/blog/2023/08/30/ozymandias-in-steam-strategy-fest.html</guid></item><item><title>Deploying Streamlit applications to Google Cloud</title><link>https://blog.christoolivier.com/p/deploying-streamlit-applications</link><description>A step-by-step guide on how to deploy and secure Streamlit applications in Google Cloud, ensuring only people in your organisation can access them</description><author>Christo’s Blog</author><pubDate>Tue, 29 Aug 2023 11:05:14 GMT</pubDate><guid isPermaLink="true">https://blog.christoolivier.com/p/deploying-streamlit-applications</guid></item><item><title>Sunset, high above the Tasman Glacier</title><link>http://michaelhoney.com/writing/2023/8/29/sunset-high-above-the-tasman-glacier</link><description>&lt;figure class="
              sqs-block-image-figure
              intrinsic
            "&gt;
          
        
        

        
          
            
          
            
                
                
                
                
                
                
                
                &lt;img alt="" height="2494" src="https://images.squarespace-cdn.com/content/v1/5244d189e4b0136e0a44003d/80ea4d9f-7bc5-43a8-847e-4de3937ac807/tasman-glacier-sunset.JPG?format=1000w" width="4433" /&gt;

            
          
        
          
        

        
      
        &lt;/figure&gt;</description><author>Michael Honey: Writing - michaelhoney.com</author><pubDate>Tue, 29 Aug 2023 07:10:51 GMT</pubDate><guid isPermaLink="true">http://michaelhoney.com/writing/2023/8/29/sunset-high-above-the-tasman-glacier</guid></item><item><title>Why you might not want to publicly self-host a Wikipedia clone</title><link>https://ounapuu.ee/posts/2023/08/29/why-you-might-not-want-to-self-host-wikipedia/</link><description>Does not apply if you like spam. No, not that one.</description><author>./techtipsy</author><pubDate>Tue, 29 Aug 2023 06:00:00 GMT</pubDate><guid isPermaLink="true">https://ounapuu.ee/posts/2023/08/29/why-you-might-not-want-to-self-host-wikipedia/</guid></item><item><title>Targeting secure browser profiles</title><link>https://seirdy.one/notes/2023/08/28/targeting-secure-browser-profiles/</link><description>&lt;p&gt;It’s hard to target browsers’ secure profiles. Safari’s Lockdown Mode disables a dozen or so APIs and a handful of other features; the Tor Browser disables another handful of features; Microsoft Edge will likely land more changes to Enhanced Security mode in the coming years. Barely any of this is documented.&lt;/p&gt;&lt;p&gt;&lt;a href="https://github.com/mdn/browser-compat-data/issues/20619"&gt;I filed a bug in MDN’s BCD tracker&lt;/a&gt; to fill this gap, listing what I knew. We can’t expect developers to navigate the dozens of WebKit blog posts and Tor Browser JSM files to figure out which features are disabled. Of course, progressive enhancement should be the norm, but it’s helpful to have a real baseline.&lt;/p&gt;</description><author>All content on Seirdy’s Home</author><pubDate>Tue, 29 Aug 2023 05:10:51 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/notes/2023/08/28/targeting-secure-browser-profiles/</guid></item><item><title>Blocking certain bots</title><link>https://seirdy.one/notes/2023/07/06/blocking-certain-bots/</link><description>&lt;aside&gt;&lt;p&gt;
	Reply to &lt;span class="h-cite u-in-reply-to"&gt;&lt;cite class="p-name"&gt;&lt;a class="u-url" href="https://gist.github.com/dangovorenefekt/b187b30e59ed1b827515cdbc833bc1bf?permalink_comment_id=4611496#gistcomment-4611496"&gt;You miss the point&lt;/a&gt;&lt;/cite&gt; by
		&lt;span class="h-card vcard p-author"&gt;&lt;a class="u-url url" href="https://govorenefekt.com/"&gt;&lt;span class="p-name fn n"&gt;DanGovorenEfekt&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;
	&lt;/span&gt;
&lt;/p&gt;&lt;/aside&gt;&lt;blockquote&gt;&lt;p&gt;I don’t want my content on those sites in any form and I don’t want my content to feed their algorithms. Using robot.txt assumes they will ‘obey’ it. But they may choose not to.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;So you’d like to block the scrapers used by some large companies, most of which appear well-behaved (i.e. they observe &lt;code&gt;robots.txt&lt;/code&gt; and &lt;code&gt;noindex&lt;/code&gt; directives). Some organizations disclose IPs they use, but you are banking on their honesty.&lt;/p&gt;&lt;p&gt;If we assume that trillion-dollar organizations are dishonest about how they scrape: they can spoof their user-agent, TLS fingerprint, IPs, etc. and use a headless browser very easily. There isn’t really a way to protect yourself from this without also excluding real users (e.g. invasive/inaccessible CAPTCHAs for which workarounds exist).&lt;/p&gt;&lt;p&gt;They can get your content without scraping by downloading other data sets like the Common Crawl (Google did this for Bard), purchasing data sets from other vendors, or acquiring other companies with their own indexes.&lt;/p&gt;&lt;p&gt;The alternative is to assume they’re at least somewhat honest about scraping content. If you use a &lt;code&gt;noindex&lt;/code&gt; robots directive in your markup and HTTP headers but allow crawling, their crawlers will visit but won’t index your site no matter what user-agent or data set they use. Check their webmaster documentation to double-check their support for these features.&lt;/p&gt;</description><author>All content on Seirdy’s Home</author><pubDate>Tue, 29 Aug 2023 03:31:03 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/notes/2023/07/06/blocking-certain-bots/</guid></item><item><title>How I Eat</title><link>https://taylor.town/how-i-eat</link><description>I'm no dietitian, but I've been eating for quite a while.</description><author>taylor.town</author><pubDate>Tue, 29 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/how-i-eat</guid></item><item><title>Vim tip 31: mark frequently used locations</title><link>https://learnbyexample.github.io/tips/vim-tip-31/</link><description>&lt;p&gt;You can save frequently visited locations using marks for quicker navigation to those positions in the file. You can also pair marks with motion commands for tasks like copying, deleting, etc.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;kbd&gt;ma&lt;/kbd&gt; mark location in the file using the alphabet &lt;code&gt;a&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;you can use any of the 26 alphabets&lt;/li&gt;
&lt;li&gt;use lowercase alphabets to work within the current file&lt;/li&gt;
&lt;li&gt;use uppercase alphabets to work from any file&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;:marks&lt;/kbd&gt; will show a list of the existing marks&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;`a&lt;/kbd&gt; move to the exact location marked by &lt;code&gt;a&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;'a&lt;/kbd&gt; move to the first non-blank character of the line marked by &lt;code&gt;a&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;'A&lt;/kbd&gt; move to the first non-blank character of the line marked by &lt;code&gt;A&lt;/code&gt; (this will work for any file where the mark was set)&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;d`a&lt;/kbd&gt; delete from the current character to the character marked by &lt;code&gt;a&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;marks can be paired with any command that accept motions like &lt;code&gt;d&lt;/code&gt;, &lt;code&gt;y&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, etc&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; Motion commands that take you across lines (for example, &lt;kbd&gt;10G&lt;/kbd&gt;) will automatically save the location you jumped from in the default &lt;code&gt;`&lt;/code&gt; mark. You can move back to that exact location using &lt;code&gt;``&lt;/code&gt; or the first non-blank character using &lt;code&gt;'`&lt;/code&gt;. Note that the arrow and word motions aren't considered for the default mark even if they move across lines.&lt;/p&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See &lt;a href="https://vimhelp.org/motion.txt.html#mark-motions"&gt;:h mark-motions&lt;/a&gt; for more ways to use marks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/vim_reference"&gt;Vim Reference Guide&lt;/a&gt; and &lt;a href="https://learnbyexample.github.io/curated_resources/vim.html"&gt;curated list of resources for Vim&lt;/a&gt;.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 29 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/vim-tip-31/</guid></item><item><title>BearerToken: The new Authentication handler in ASP.NET Core 8</title><link>https://nestenius.se/net/bearertoken-the-new-authentication-handler-in-net-8/</link><description>Microsoft introduced the new BearerToken authentication handler in ASP.NET Core 8 as part of an initiative to streamline and modernize authentication</description><author>Tore Nestenius</author><pubDate>Tue, 29 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://nestenius.se/net/bearertoken-the-new-authentication-handler-in-net-8/</guid></item><item><title>Freier Fall: ein Gedicht über die Migrantenpsyche</title><link>https://jdsalaro.com/poem/freier-fall-migrantenspsyche/</link><description>Dieses Gedicht wurde ursprünglich für den Akt von KAMINA1 beim Heidelberger Literaturherbst 20232 geschrieben.</description><author>Jayson Salazar Rodriguez | @jdsalaro | Blog</author><pubDate>Tue, 29 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jdsalaro.com/poem/freier-fall-migrantenspsyche/</guid></item><item><title>Fingerprinting and customization</title><link>https://seirdy.one/notes/2023/08/28/fingerprinting-and-customization/</link><description>&lt;aside&gt;&lt;p&gt;
	Reply to &lt;span class="h-cite u-in-reply-to"&gt;&lt;cite class="p-name"&gt;&lt;a class="u-url" href="https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/40337#note_2936949"&gt;Wouldn't, say, installing the Dark Reader extension have much less of a privacy impact than disabling RFP altogether?&lt;/a&gt;&lt;/cite&gt; by
		&lt;span class="h-card vcard p-author"&gt;&lt;a class="u-url url" href="https://gitlab.torproject.org/Allium"&gt;&lt;span class="p-name fn n"&gt;Allium&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;
	&lt;/span&gt;
&lt;/p&gt;&lt;/aside&gt;&lt;p&gt;These addons work by injecting or altering stylesheets in the page, and are trivially detectable. A good rule of thumb is that if it can trigger a CSP violation in the developer console, it is trivial to detect with JavaScript.&lt;/p&gt;&lt;p&gt;(FWIW: I believe the Tor Browser does disable the Reporting API, so I think some JavaScript will be necessary).&lt;/p&gt;&lt;p&gt;On “safest” mode with remote JavaScript disabled, certain “dark mode” addons &lt;em&gt;might&lt;/em&gt; be safe. I think a better long-term solution would be the ability to “freeze” a page: a button or something to prevent the current page from initiating further requests (it’s already loaded), running scripts, accessing storage, etc. In this state, a user could use any addons or fingerprinting-compromising settings without risk.&lt;/p&gt;&lt;p&gt;A good point of comparison is Reader Mode: a user’s preferred Reader Mode fonts, line-width, color scheme, etc. aren’t fingerprinting vectors. It should be able to stop a site from phoning home or writing to client-side storage to allow for similar levels of customization outside Reader Mode.&lt;/p&gt;&lt;p&gt;Other sources of inspiration could be the expected behavior for the &lt;a href="https://drafts.csswg.org/mediaqueries-5/#scripting"&gt;&lt;code&gt;scripting: initial-only&lt;/code&gt; media query&lt;/a&gt;, and Firefox’s built-in “Work Offline” setting.&lt;/p&gt;</description><author>All content on Seirdy’s Home</author><pubDate>Mon, 28 Aug 2023 23:52:35 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/notes/2023/08/28/fingerprinting-and-customization/</guid></item><item><title>Keep calm and carry on: Taming the siren call of overwhelm and overwork</title><link>https://abdulapopoola.com/2023/08/28/keep-calm-and-carry-on-taming-the-siren-call-of-overwhelm-and-overwork/</link><description>If you are constantly overwhelmed and too busy - something somewhere somehow is wrong. Read on to learn more about my journey from an overwhelmed, feisty, flustered newbie to an equanimous, calm, collected professional with arguably more impact.</description><author>CodeKraft</author><pubDate>Mon, 28 Aug 2023 18:00:00 GMT</pubDate><guid isPermaLink="true">https://abdulapopoola.com/2023/08/28/keep-calm-and-carry-on-taming-the-siren-call-of-overwhelm-and-overwork/</guid></item><item><title>Slow learnings from 15 years of personal projects</title><link>https://medium.com/@k0ryk/resolutions-writing-and-robots-69909b4dad6d?source=rss-3cf45d4f3676------2</link><description>&lt;p&gt;I have been writing one article a week since May. A challenge I was undertaking before realizing I had accepted it. I had to start writing to realize that I wanted to continue writing. I plan on keeping it up until the new year. Starting January 2024, I plan to write two a month.&lt;/p&gt;&lt;p&gt;Some people begin their year with a list of things they want to do, maybe some resolutions. January feels so long ago. I recall ambivalence; if I had a resolution, I don’t remember it. Whatever I was thinking then, it was not that I needed to start a blog and write weekly articles.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/700/0*PnSus9uDCqK_-uqO.png" /&gt;&lt;figcaption&gt;Growing my garden from seed was one of my goals this year. &lt;a href="https://medium.com/@k0ryk/raspberry-pi-pivot-419071210040"&gt;Full write up here.&lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;If I take it from an Software Engineering or Agile perspective, I might say something like: “A New Years resolution is too large batch for me. Let’s split up my year into two-week resolution-sprints with a monthly demo and quarterly planning.” It’s tempting to take the patterns that work for me professionally and bring them into my own projects, but it doesn’t work. Those don’t scale down from X to 1. So, instead of two-week sprints, I find myself governed by a more fluid set of goals.&lt;/p&gt;&lt;p&gt;Even though I do not consciously plan out my year in resolution-sprints. It still kind of turns out that way. I have a long-running constantly shifting queue of goals, that’s how it plays out. I regularly quote my favorite line in the &lt;em&gt;Agile Manifesto&lt;/em&gt; “responding to change over following a plan.” I don’t need a new year resolution, because in January I am still maintaining whatever I was working on in December. So far this year I’ve built games, bots, APIs, home automations. I have done a lot around the house for home improvement, and have had some gardening projects. I have had a lot of successful projects and some devastating failures. Most of that wasn’t on the docket in January, including writing.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/320/1*HrEpD7sC41bW-e3SeWXYBw.gif" /&gt;&lt;figcaption&gt;One of my devastating failures. Not only were most of these sprouts incredibly root locked by the seed starter, but a few days after planting outside, a large hail storm pulped the majority of them.&lt;/figcaption&gt;&lt;/figure&gt;&lt;h4&gt;My history of writing&lt;/h4&gt;&lt;p&gt;I write a lot of things down; typically, the audience is myself, or maybe a small group of people at work, friends, or family. I am fastidious note taker and list maker. Before May of this year, I rarely formally transposed my thoughts for others outside of technical wiki pages, discord servers and group texts.&lt;/p&gt;&lt;p&gt;I’ve been coming up with project ideas since my teenage years. Projects that are big, silly, idealistic and imaginative. Looking through some of my old work, I see a design from when I was 20 years old in a journal labelled brain.dmp . The first dozen pages are dedicated to a design for a submersible robot that cleans plastic. The second is for a website that generates PDFs of stencils meant for spray-painting — intended to fit over signs with imperial units so you could adorn them with their metric equivalent; a domain name purchased and never fully used.&lt;/p&gt;&lt;p&gt;I have many of these types of journals, some of them full. Some of them left partially empty intentionally to make room for a thought that will never be finished, that you must capture the essence while it’s fresh in your mind. These are projects I have yet to do, and I don’t know if I will ever do them.&lt;/p&gt;&lt;p&gt;Writing has been a way to get the projects in my head into a more permanent place. In doing so, they become real; drawing boxes and lines that were only imagined before requires you to make sense of the project and correct the internal inconsistencies. This effect is even greater when the writing is put out for public consumption.&lt;/p&gt;&lt;h4&gt;A snapshot of the past&lt;/h4&gt;&lt;p&gt;Reading my old technical notes and journals, I feel proud and slightly embarrassed. I was so idealistic and wanted to take on things that were unachievably huge for me. I was focused on the impact and aesthetic — making sketches of logos and brainstorming names for things before ever writing a single line of code. I didn’t prioritize the right way, and didn’t know how to set myself up for success.&lt;/p&gt;&lt;p&gt;I think my ambivalence towards my old writings is also wrapped up in the fact that I didn’t deliver the vast majority of the projects I undertook. I liked talking about the ideas, but had trouble finding a path forward with them. I found the greatest success in projects that I could make meaningful progress on in a matter of hours.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/282/1*VGZTQWqxqISP5Kkq7fuOpw.png" /&gt;&lt;figcaption&gt;Some of the large batch projects in my backlog. I should remove the top one as it’s obsolete with LLMs.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;I continued to plan work that I would never do, but my ability to dedicate some time every weekend to improve or build a new project slowly shifted my focus away from large batch projects, many of them focused on Home Automation. As I found success, even my large batch projects became implemented this way, one step at a time.&lt;/p&gt;&lt;h4&gt;Generative AI as dev team&lt;/h4&gt;&lt;p&gt;It would be hard to talk about my personal development since the beginning of the year without talking about generative AI as weird as that sounds. It’s one of the inspirations for my writing. I want the work I have here to be representative of my voice. My writing is an offering to the next iterations of generative AI, my way of say “this is my style.”&lt;/p&gt;&lt;p&gt;I use generative AI heavily, but not usually for writing. I treat it like a junior developer. It can help bootstrap a project a lot more quickly than I could alone. In this sense, I feel empowered as a solo project-doer. One example of this is for making games. I am not a strong artist and this has caused me to seek out artists to make games with. Generative AI allows me to draw the structure of the art and choose from a bunch of different beautiful versions of it.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9xbdAc5vDusn_xjEII5brQ.png" /&gt;&lt;figcaption&gt;pre-alpha screenshot of one of my projects. The background and cards are all created with generative AI&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Reducing the barrier to entry is a huge part of why generative AI helps me, and not just in the artistic realm. I can have it complete tedious tasks in parallel with what I am doing. In some ways it’s like pair programming. “write a python script to take an array of json blobs and download any image urls in them to a directory called files and have its name be the same as the timestamp.” It’s way easier for me to type that than write the code. Even if I could write the code pretty fast and be less error prone, I now can use the mental energy that I would spend focused on that going to the next task while I wait for my AI jr. dev to finish the code block.&lt;/p&gt;&lt;h4&gt;Final Thoughts&lt;/h4&gt;&lt;p&gt;A decade of delivery for my own projects means those projects in those old journals feel distant. The hand writing is the same and so is my inability to create a litter-collecting submersible robot in my basement. What also hasn’t changed is my yearn to have an impact on the causes I care about (like saving the earth). My mindset has definitely shifted though; impact delivered is better than impact planned. Thank you for reading.&lt;/p&gt;&lt;p&gt;Here’s what I am planning between now and the end of the year (in no specific order).&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Upcoming articles:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Raspberry Pi series Part 3 —Prometheus + Grafana and USB and Bluetooth based sensors for home automation w/ Home Assistant&lt;/li&gt;&lt;li&gt;SciFi Author as Product Owner Part 2 — Gamma level simulations&lt;/li&gt;&lt;li&gt;SciFi Author as Product Owner Part 3 — Kim Stanley Robinson’s Mars series AI Tablets&lt;/li&gt;&lt;li&gt;Software Engineering musings&lt;/li&gt;&lt;li&gt;Philosophical musings&lt;/li&gt;&lt;li&gt;Personal musings&lt;/li&gt;&lt;/ul&gt;&lt;img alt="" height="1" src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=69909b4dad6d" width="1" /&gt;</description><author>Stories by Kory Kirk on Medium</author><pubDate>Mon, 28 Aug 2023 15:44:51 GMT</pubDate><guid isPermaLink="true">https://medium.com/@k0ryk/resolutions-writing-and-robots-69909b4dad6d?source=rss-3cf45d4f3676------2</guid></item><item><title>A Funny Thing I Noticed About Western Culture</title><link>https://matteoraso.github.io/a-funny-thing-i-noticed-about-western-culture.html</link><description>&lt;p&gt;I have a bit of an interest in fashion, and I've been thinking about why we don't really wear formal clothing that much. It clicked to me that the reason is because western culture isn't really that formal. Maybe that sounds obvious to some people, but as somebody who's been …&lt;/p&gt;</description><author>Matteo's Website</author><pubDate>Mon, 28 Aug 2023 07:00:00 GMT</pubDate><guid isPermaLink="true">https://matteoraso.github.io/a-funny-thing-i-noticed-about-western-culture.html</guid></item><item><title>August 28, 2023</title><link>https://brntn.me/notes/LPqehFyLwx4/</link><description>&lt;div class="h-review"&gt;
                &lt;p&gt;
                    📺 Watched
                    &lt;span class="h-item"&gt;
                        &lt;a class="p-name u-url u-watch-of" href="https://www.imdb.com/title/tt0472954/"&gt;It's Always Sunny in Philadelphia (Season 16)&lt;/a&gt;.
            ★★★★&lt;/span&gt;.
                &lt;/p&gt;&lt;p&gt;&lt;img alt="Poster for It" src="https://media.brntn.me/postie/a64be74c.jpg" /&gt;&lt;/p&gt;&lt;/div&gt;</description><author>Brenton Cleeland</author><pubDate>Mon, 28 Aug 2023 06:17:02 GMT</pubDate><guid isPermaLink="true">https://brntn.me/notes/LPqehFyLwx4/</guid></item><item><title>Binary Ninja! 🔪🍉 - CSS Edition</title><link>https://benjcal.space/blog/binary-ninja-css-edition/</link><description>&lt;h3 id="binary-ninja"&gt;🥷 Binary Ninja!&lt;/h3&gt;
&lt;p&gt;When learning a new programming language there's usually a section that I look at, expressly to &lt;em&gt;not&lt;/em&gt; to use! You see, for whatever reason most languages would ship with a &lt;em&gt;real&lt;/em&gt; &amp;quot;AND&amp;quot;, and then with &lt;em&gt;another&lt;/em&gt; &amp;quot;AND&amp;quot;, just confuse me! It'd be like this in JS, for example:&lt;/p&gt;
&lt;pre class="language-js" tabindex="0"&gt;&lt;code class="language-js"&gt;&lt;span class="token comment"&gt;// "real" AND&lt;/span&gt;
&lt;span class="token boolean"&gt;true&lt;/span&gt; &lt;span class="token operator"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="token boolean"&gt;false&lt;/span&gt;&lt;span class="token punctuation"&gt;;&lt;/span&gt; &lt;span class="token comment"&gt;// =&gt; false&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and then there's this... this &lt;em&gt;thing&lt;/em&gt;!&lt;/p&gt;
&lt;pre class="language-js" tabindex="0"&gt;&lt;code class="language-js"&gt;&lt;span class="token comment"&gt;// what in the world!?&lt;/span&gt;
&lt;span class="token boolean"&gt;true&lt;/span&gt; &lt;span class="token operator"&gt;&amp;amp;&lt;/span&gt; &lt;span class="token boolean"&gt;false&lt;/span&gt;&lt;span class="token punctuation"&gt;;&lt;/span&gt; &lt;span class="token comment"&gt;// =&gt; 0&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same thing with OR &lt;code&gt;|| vs. |&lt;/code&gt; and with NOT &lt;code&gt;! vs. ~&lt;/code&gt; and a few others.&lt;/p&gt;
&lt;p&gt;Well, it seems like some wise people called those bit&lt;em&gt;wise&lt;/em&gt; operator, and for the longest time I've intentionally avoided them, and got scared when I came across code that used them! Well, on the &lt;a href="https://benjcal.space/blog/the-adventures-of-writing-a-chip8-emulator-part-2"&gt;CHIP8 project&lt;/a&gt; I had no choice but to face my fears and in this post I want to share with you how cool bitwise operator really are! My goal is not to be exhaustive and tell you everything about them, your programming language manual would be the place for that. Instead I want to share with you a use case and hopefully encourage you to find out more and add them to your programming toolbox!&lt;/p&gt;
&lt;p&gt;Ok, buckle up and let's ge to it!&lt;/p&gt;
&lt;p&gt;And it all starts with the name.&lt;/p&gt;
&lt;h3 id="bitwise-some-wise-person-once-said-about-a-bit-maybe-sorry"&gt;Bitwise, some wise person once said about a bit... maybe. (sorry) 🤦‍♂️&lt;/h3&gt;
&lt;p&gt;Paying close attention to the name of these operators we get the biggest clue we need. Bit! The bitwise operators, curiously enough, operate on bits.&lt;/p&gt;
&lt;p&gt;Back in the day, before every developer everywhere worked solely inside an &lt;code&gt;App.jsx&lt;/code&gt; file, people used to think about how the data inside the computer looked like. You see, computers are not too clever. They only know how to say two things: &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;1&lt;/code&gt;. That is all. When you put a bunch of those together and assign meaning to them you can fool yourself into thinking that computers are clever. Nah, people are, but people got tired of being clever and thus made computers to pretend to be instead of them.&lt;/p&gt;
&lt;p&gt;Anyways, it is 2023, so let's get on with the times and do what everybody is doing nowadays, making websites pretty! 🎨&lt;/p&gt;
&lt;p&gt;Imagine that we have the color &lt;a href="https://www.w3schools.com/colors/color_tryit.asp?color=OliveDrab"&gt;OliveDrab&lt;/a&gt; (not to be confused with a drab olive, which might be moldy and thus not recommended for human consumption). That color in Hex is &lt;code&gt;0x6B8E23&lt;/code&gt;, of you might see it as &lt;code&gt;#6B8E23&lt;/code&gt; in a CSS file, same thing. Well, from our frontend dev knowledge we know that that number is actually three numbers that represent red, green, and blue values. And from a quick glance we can see that red would be &lt;code&gt;0x6B&lt;/code&gt;, green &lt;code&gt;0x8E&lt;/code&gt;, and blue &lt;code&gt;0x23&lt;/code&gt;. Well, that's easy, but that's because you are not a computer! The computer only sees &lt;code&gt;0110 1011 1000 1110 0010 0011&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So anyways, all the computer sees is &lt;code&gt;0x6B8E23&lt;/code&gt; as one number, which in decimal is the number &lt;code&gt;7048739&lt;/code&gt; or the binary &lt;code&gt;0110 1011 1000 1110 0010 0011&lt;/code&gt; without any notion that there actually three pieces of data in that one number.&lt;/p&gt;
&lt;p&gt;Now, we have received the task to convert all the hex numbers in our CSS files to rgb values (I know is hard to imagine such a ridiculous and meaningless task, but have you ever worked in tech?). Well, that's where our bitwise operators come to help us!&lt;/p&gt;
&lt;p&gt;Let's start with the &lt;em&gt;wrong&lt;/em&gt; &amp;quot;AND&amp;quot; &lt;code&gt;&amp;amp;&lt;/code&gt;. This, obviously, can be used to perform the logic AND function on two number, but is can also be used to mask bits (The term &amp;quot;mask&amp;quot; confused the heck out of me for many years! Hang on and hopefully after this it might make sense). By making we mean that it can let some bits &amp;quot;pass&amp;quot; and some others don't. Another way to think about it is, it can &amp;quot;hide&amp;quot; bits we don't care about.&lt;/p&gt;
&lt;p&gt;Let's give it a try!&lt;/p&gt;
&lt;pre class="language-c" tabindex="0"&gt;&lt;code class="language-c"&gt;&lt;span class="token number"&gt;0x6B8E23&lt;/span&gt; &lt;span class="token operator"&gt;&amp;amp;&lt;/span&gt; &lt;span class="token number"&gt;0x0000FF&lt;/span&gt; &lt;span class="token comment"&gt;// =&gt; 0x23&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Would you look at that! We where able to get rid of all the pesky red and green and now we have the value of blue by itself. If you run this code you might see the value &lt;code&gt;35&lt;/code&gt;, which is the value of &lt;code&gt;0x23&lt;/code&gt; in decimal.&lt;/p&gt;
&lt;p&gt;Very nifty, but what is happening? Why &lt;code&gt;0xFF&lt;/code&gt;? Well, let's look at the... ready? Bits!&lt;/p&gt;
&lt;pre class="language-js" tabindex="0"&gt;&lt;code class="language-js"&gt;  &lt;span class="token number"&gt;0110&lt;/span&gt; &lt;span class="token number"&gt;1011&lt;/span&gt; &lt;span class="token number"&gt;1000&lt;/span&gt; &lt;span class="token number"&gt;1110&lt;/span&gt; &lt;span class="token number"&gt;0010&lt;/span&gt; &lt;span class="token number"&gt;0011&lt;/span&gt;    &lt;span class="token comment"&gt;// =&gt; 0x6B8E23&lt;/span&gt;
&lt;span class="token operator"&gt;&amp;amp;&lt;/span&gt; &lt;span class="token number"&gt;0000&lt;/span&gt; &lt;span class="token number"&gt;0000&lt;/span&gt; &lt;span class="token number"&gt;0000&lt;/span&gt; &lt;span class="token number"&gt;0000&lt;/span&gt; &lt;span class="token number"&gt;1111&lt;/span&gt; &lt;span class="token number"&gt;1111&lt;/span&gt;    &lt;span class="token comment"&gt;// =&gt; 0xFF&lt;/span&gt;
  &lt;span class="token operator"&gt;--&lt;/span&gt;&lt;span class="token operator"&gt;--&lt;/span&gt;&lt;span class="token operator"&gt;--&lt;/span&gt;&lt;span class="token operator"&gt;--&lt;/span&gt;&lt;span class="token operator"&gt;--&lt;/span&gt;&lt;span class="token operator"&gt;--&lt;/span&gt;&lt;span class="token operator"&gt;--&lt;/span&gt;&lt;span class="token operator"&gt;--&lt;/span&gt;&lt;span class="token operator"&gt;--&lt;/span&gt;&lt;span class="token operator"&gt;--&lt;/span&gt;&lt;span class="token operator"&gt;--&lt;/span&gt;&lt;span class="token operator"&gt;--&lt;/span&gt;&lt;span class="token operator"&gt;--&lt;/span&gt;&lt;span class="token operator"&gt;--&lt;/span&gt;&lt;span class="token operator"&gt;-&lt;/span&gt;
  &lt;span class="token number"&gt;0000&lt;/span&gt; &lt;span class="token number"&gt;0000&lt;/span&gt; &lt;span class="token number"&gt;0000&lt;/span&gt; &lt;span class="token number"&gt;0000&lt;/span&gt; &lt;span class="token number"&gt;0010&lt;/span&gt; &lt;span class="token number"&gt;0011&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Uff, that's a bunch of zeros and ones! But notice what is happening, when we &lt;code&gt;&amp;amp;&lt;/code&gt; the original number with our &amp;quot;mask&amp;quot;, all the places where &lt;em&gt;both&lt;/em&gt; the original &lt;code&gt;1&lt;/code&gt; and our mask are &lt;code&gt;1&lt;/code&gt; result in &lt;code&gt;1&lt;/code&gt;. In the places where our mask is &lt;code&gt;0&lt;/code&gt; the result is &lt;code&gt;0&lt;/code&gt; regardless.&lt;/p&gt;
&lt;p&gt;It's like multiplying! Which is exactly right! Bitwise AND can be &lt;em&gt;sort of&lt;/em&gt; thought of as multiplication in boolean algebra, while bitwise OR can be thought as addition.&lt;/p&gt;
&lt;p&gt;As you might have noticed, writing number in binary is tending towards the painful side of things. Let's start using the hexadecimal number system, or hex for short to both, save some typing and possible errors. A hex number let's us represent each group of four bits (a nibble), with one number/letter between &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;F&lt;/code&gt;. So, from our color above you can see how each nibble (4 bits) represent one number/letter on the hex number (&lt;code&gt;3 == 0011&lt;/code&gt;, &lt;code&gt;2 == 0010&lt;/code&gt;, &lt;code&gt;E == 1110&lt;/code&gt;, and so on).&lt;/p&gt;
&lt;p&gt;Awesome, let's keep going, how about getting the green value?&lt;/p&gt;
&lt;pre class="language-c" tabindex="0"&gt;&lt;code class="language-c"&gt;&lt;span class="token number"&gt;0x6B8E23&lt;/span&gt; &lt;span class="token operator"&gt;&amp;amp;&lt;/span&gt; &lt;span class="token number"&gt;0xFF00&lt;/span&gt; &lt;span class="token comment"&gt;// =&gt; 0x8E00&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Well, that's progress, we are able to get &lt;code&gt;8E&lt;/code&gt; alone, but there are two extra &lt;code&gt;00&lt;/code&gt; there. How do we get rid of them? This is the Ninja part!&lt;/p&gt;
&lt;h3 id="shift-like-you-are-driving-manual"&gt;Shift like you are driving manual! 🏎️&lt;/h3&gt;
&lt;p&gt;Among the bitwise operators there are these curious looking &lt;code&gt;&amp;lt;&amp;lt;&lt;/code&gt; and &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt; operators. Initially what they do might seem obscure. I mean with a description like this from &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Left_shift"&gt;mdn web docs&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The left shift (&amp;lt;&amp;lt;) operator returns a number or BigInt whose binary representation is the first operand shifted by the specified number of bits to the left.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It's accurate, terse, and initially left me like, &lt;em&gt;wat&lt;/em&gt;?&lt;/p&gt;
&lt;p&gt;But let's look what it does, maybe we can spot a pattern or something.&lt;/p&gt;
&lt;pre class="language-c" tabindex="0"&gt;&lt;code class="language-c"&gt;&lt;span class="token number"&gt;1&lt;/span&gt; &lt;span class="token operator"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="token number"&gt;0&lt;/span&gt; &lt;span class="token comment"&gt;// =&gt; 0000 0001&lt;/span&gt;
&lt;span class="token number"&gt;1&lt;/span&gt; &lt;span class="token operator"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="token number"&gt;1&lt;/span&gt; &lt;span class="token comment"&gt;// =&gt; 0000 0010&lt;/span&gt;
&lt;span class="token number"&gt;1&lt;/span&gt; &lt;span class="token operator"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="token number"&gt;2&lt;/span&gt; &lt;span class="token comment"&gt;// =&gt; 0000 0100&lt;/span&gt;
&lt;span class="token number"&gt;1&lt;/span&gt; &lt;span class="token operator"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="token number"&gt;3&lt;/span&gt; &lt;span class="token comment"&gt;// =&gt; 0000 1000&lt;/span&gt;
&lt;span class="token number"&gt;1&lt;/span&gt; &lt;span class="token operator"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="token number"&gt;4&lt;/span&gt; &lt;span class="token comment"&gt;// =&gt; 0001 0000&lt;/span&gt;
&lt;span class="token number"&gt;1&lt;/span&gt; &lt;span class="token operator"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="token number"&gt;5&lt;/span&gt; &lt;span class="token comment"&gt;// =&gt; 0010 0000&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Did you see that? All that that operator is doing is shifting to the left the &lt;code&gt;1&lt;/code&gt; on the left by the number on the right. Not bad!&lt;/p&gt;
&lt;p&gt;As you might have guessed we can shift something else than &lt;code&gt;1&lt;/code&gt;, say, &lt;code&gt;6&lt;/code&gt; (0110) and it would shift that whole number too.&lt;/p&gt;
&lt;pre class="language-c" tabindex="0"&gt;&lt;code class="language-c"&gt;&lt;span class="token number"&gt;6&lt;/span&gt; &lt;span class="token operator"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="token number"&gt;0&lt;/span&gt; &lt;span class="token comment"&gt;// =&gt; 0000 0110&lt;/span&gt;
&lt;span class="token number"&gt;6&lt;/span&gt; &lt;span class="token operator"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="token number"&gt;1&lt;/span&gt; &lt;span class="token comment"&gt;// =&gt; 0000 1100&lt;/span&gt;
&lt;span class="token number"&gt;6&lt;/span&gt; &lt;span class="token operator"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="token number"&gt;2&lt;/span&gt; &lt;span class="token comment"&gt;// =&gt; 0001 1000&lt;/span&gt;
&lt;span class="token number"&gt;6&lt;/span&gt; &lt;span class="token operator"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="token number"&gt;3&lt;/span&gt; &lt;span class="token comment"&gt;// =&gt; 0011 0000&lt;/span&gt;
&lt;span class="token number"&gt;6&lt;/span&gt; &lt;span class="token operator"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="token number"&gt;4&lt;/span&gt; &lt;span class="token comment"&gt;// =&gt; 0110 0000&lt;/span&gt;
&lt;span class="token number"&gt;6&lt;/span&gt; &lt;span class="token operator"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="token number"&gt;5&lt;/span&gt; &lt;span class="token comment"&gt;// =&gt; 1100 0000&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we can do it reverse too!&lt;/p&gt;
&lt;pre class="language-c" tabindex="0"&gt;&lt;code class="language-c"&gt;&lt;span class="token number"&gt;0010&lt;/span&gt; &lt;span class="token number"&gt;0000&lt;/span&gt; &lt;span class="token operator"&gt;&gt;&gt;&lt;/span&gt; &lt;span class="token number"&gt;1&lt;/span&gt; &lt;span class="token comment"&gt;// =&gt; 0001 0000&lt;/span&gt;
&lt;span class="token number"&gt;0010&lt;/span&gt; &lt;span class="token number"&gt;0000&lt;/span&gt; &lt;span class="token operator"&gt;&gt;&gt;&lt;/span&gt; &lt;span class="token number"&gt;2&lt;/span&gt; &lt;span class="token comment"&gt;// =&gt; 0000 1000&lt;/span&gt;
&lt;span class="token number"&gt;0010&lt;/span&gt; &lt;span class="token number"&gt;0000&lt;/span&gt; &lt;span class="token operator"&gt;&gt;&gt;&lt;/span&gt; &lt;span class="token number"&gt;3&lt;/span&gt; &lt;span class="token comment"&gt;// =&gt; 0000 0100&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this now we know how to get rid of the extra 0s in &lt;code&gt;0x8E00&lt;/code&gt;!&lt;/p&gt;
&lt;pre class="language-c" tabindex="0"&gt;&lt;code class="language-c"&gt;&lt;span class="token number"&gt;0x8E00&lt;/span&gt; &lt;span class="token operator"&gt;&gt;&gt;&lt;/span&gt; &lt;span class="token number"&gt;2&lt;/span&gt; &lt;span class="token comment"&gt;// =&gt; 0x2380&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Wait, what? (I spent and embarrassingly amount of time trying to figure this one out...)&lt;/p&gt;
&lt;p&gt;Remember the nibbles up there? how four bits represent one digit in hex? That means that to get rid of one &lt;code&gt;0&lt;/code&gt; in hex we need to get rid of 4 bits. And thus, to get rid of a whole nibbles we'd need to shift in multiples of 4.&lt;/p&gt;
&lt;pre class="language-c" tabindex="0"&gt;&lt;code class="language-c"&gt;&lt;span class="token number"&gt;0x8E00&lt;/span&gt; &lt;span class="token operator"&gt;&gt;&gt;&lt;/span&gt; &lt;span class="token number"&gt;4&lt;/span&gt; &lt;span class="token comment"&gt;// =&gt; 0x8E0 --- closer!&lt;/span&gt;
&lt;span class="token number"&gt;0x8E00&lt;/span&gt; &lt;span class="token operator"&gt;&gt;&gt;&lt;/span&gt; &lt;span class="token number"&gt;8&lt;/span&gt; &lt;span class="token comment"&gt;// =&gt; 0x8E  --- let's go!!&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ok, let's recap super quick where are we, we can use &lt;code&gt;&amp;amp;&lt;/code&gt; to mask out the stuff that we don't want from a number, and then we can shift away the number that we don't want. Thus in order to get the green value out of &lt;code&gt;0x6B8E23&lt;/code&gt; we would do:&lt;/p&gt;
&lt;pre class="language-c" tabindex="0"&gt;&lt;code class="language-c"&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;&lt;span class="token number"&gt;0x6B8E23&lt;/span&gt; &lt;span class="token operator"&gt;&amp;amp;&lt;/span&gt; &lt;span class="token number"&gt;0x00FF00&lt;/span&gt;&lt;span class="token punctuation"&gt;)&lt;/span&gt; &lt;span class="token operator"&gt;&gt;&gt;&lt;/span&gt; &lt;span class="token number"&gt;8&lt;/span&gt; &lt;span class="token comment"&gt;// =&gt; 0x8E&lt;/span&gt;

&lt;span class="token punctuation"&gt;(&lt;/span&gt;&lt;span class="token number"&gt;0x6B8E23&lt;/span&gt; &lt;span class="token operator"&gt;&amp;amp;&lt;/span&gt; &lt;span class="token number"&gt;0xFF0000&lt;/span&gt;&lt;span class="token punctuation"&gt;)&lt;/span&gt; &lt;span class="token operator"&gt;&gt;&gt;&lt;/span&gt; &lt;span class="token number"&gt;16&lt;/span&gt; &lt;span class="token comment"&gt;// =&gt; 0x6B&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's neat! Now we can very easily get our values for our hex CSS ro rgb function&lt;/p&gt;
&lt;pre class="language-js" tabindex="0"&gt;&lt;code class="language-js"&gt;&lt;span class="token keyword"&gt;let&lt;/span&gt; hexColor &lt;span class="token operator"&gt;=&lt;/span&gt; &lt;span class="token number"&gt;0x6b8e23&lt;/span&gt;&lt;span class="token punctuation"&gt;;&lt;/span&gt;

&lt;span class="token keyword"&gt;let&lt;/span&gt; r &lt;span class="token operator"&gt;=&lt;/span&gt; &lt;span class="token punctuation"&gt;(&lt;/span&gt;&lt;span class="token number"&gt;0x6b8e23&lt;/span&gt; &lt;span class="token operator"&gt;&amp;amp;&lt;/span&gt; &lt;span class="token number"&gt;0xff0000&lt;/span&gt;&lt;span class="token punctuation"&gt;)&lt;/span&gt; &lt;span class="token operator"&gt;&gt;&gt;&lt;/span&gt; &lt;span class="token number"&gt;16&lt;/span&gt;&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;span class="token keyword"&gt;let&lt;/span&gt; g &lt;span class="token operator"&gt;=&lt;/span&gt; &lt;span class="token punctuation"&gt;(&lt;/span&gt;&lt;span class="token number"&gt;0x6b8e23&lt;/span&gt; &lt;span class="token operator"&gt;&amp;amp;&lt;/span&gt; &lt;span class="token number"&gt;0x00ff00&lt;/span&gt;&lt;span class="token punctuation"&gt;)&lt;/span&gt; &lt;span class="token operator"&gt;&gt;&gt;&lt;/span&gt; &lt;span class="token number"&gt;8&lt;/span&gt;&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;span class="token keyword"&gt;let&lt;/span&gt; b &lt;span class="token operator"&gt;=&lt;/span&gt; &lt;span class="token punctuation"&gt;(&lt;/span&gt;&lt;span class="token number"&gt;0x6b8e23&lt;/span&gt; &lt;span class="token operator"&gt;&amp;amp;&lt;/span&gt; &lt;span class="token number"&gt;0x0000ff&lt;/span&gt;&lt;span class="token punctuation"&gt;)&lt;/span&gt; &lt;span class="token operator"&gt;&gt;&gt;&lt;/span&gt; &lt;span class="token number"&gt;0&lt;/span&gt;&lt;span class="token punctuation"&gt;;&lt;/span&gt;

console&lt;span class="token punctuation"&gt;.&lt;/span&gt;&lt;span class="token function"&gt;log&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;&lt;span class="token template-string"&gt;&lt;span class="token template-punctuation string"&gt;`&lt;/span&gt;&lt;span class="token string"&gt;the rbg value is rgb(&lt;/span&gt;&lt;span class="token interpolation"&gt;&lt;span class="token interpolation-punctuation punctuation"&gt;${&lt;/span&gt;r&lt;span class="token interpolation-punctuation punctuation"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class="token string"&gt;, &lt;/span&gt;&lt;span class="token interpolation"&gt;&lt;span class="token interpolation-punctuation punctuation"&gt;${&lt;/span&gt;g&lt;span class="token interpolation-punctuation punctuation"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class="token string"&gt;, &lt;/span&gt;&lt;span class="token interpolation"&gt;&lt;span class="token interpolation-punctuation punctuation"&gt;${&lt;/span&gt;b&lt;span class="token interpolation-punctuation punctuation"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class="token string"&gt;)&lt;/span&gt;&lt;span class="token template-punctuation string"&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class="token punctuation"&gt;)&lt;/span&gt;&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;span class="token comment"&gt;// the rbg value is rgb(107, 142, 35)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Et Voilà!&lt;/p&gt;
&lt;p&gt;There's definitively a lot more to binary an bitwise operators, we just learned a little bit (or a nibble... I'm so sorry) of the chopping part, we can stick them together (imagine another ridiculous task to convert all the rgb values to hex 🤦‍♂️), we can... well, do more, but chopping and sticking stuff is enough for a &lt;a href="https://benjcal.space/blog/the-adventures-of-writing-a-chip8-emulator-part-2"&gt;CHIP8 emulator&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See you on the next time!&lt;/p&gt;</description><author>Ben's Web Space</author><pubDate>Mon, 28 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://benjcal.space/blog/binary-ninja-css-edition/</guid></item><item><title>Changing my relationship with GitHub Copilot</title><link>https://ntietz.com/blog/changing-my-relationship-with-github-copilot/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;I've been using GitHub Copilot on personal projects since March.
It's been an interesting experience, and one that I realized I have to change.
Using Copilot nearly full time has had some positive and  negative impacts on me, and it's time to take control of how I interact with it.&lt;/p&gt;
&lt;h1 id="the-honeymoon-phase"&gt;The honeymoon phase&lt;/h1&gt;
&lt;p&gt;I've liked a lot about working with Copilot.
For this duration I've had Copilot enabled full-time in code files&lt;sup class="footnote-reference" id="fr-1-1"&gt;&lt;a href="https://ntietz.com/blog/changing-my-relationship-with-github-copilot/#fn-1"&gt;[1]&lt;/a&gt;&lt;/sup&gt;.
I pretty quickly found that I was able to be productive in ways that I was struggling with before due to life circumstances.
With two kids, a full-time job, a time-consuming running habit, and ongoing medical treatment, by the time I sit down to write code for myself, I'm just &lt;em&gt;tired&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;But with Copilot enabled, it was a lot easier to actually write code in the evenings even when I was tired.
I could put in simple comments as prompts and get back out something that mostly-sorta worked, then shape it into what I needed.&lt;/p&gt;
&lt;p&gt;This led me to get some work done on small projects and a few assorted scripts.
I made progress on an issue tracker I am working on with a friend.
I wrote the parser and formatter for my &lt;a href="https://ntietz.com/blog/introducing-hurl/"&gt;programming language&lt;/a&gt;.
This was pretty good work, and I was able to get it done even when tired!
It seemed like everything was going well.&lt;/p&gt;
&lt;h1 id="cracks-form"&gt;Cracks form&lt;/h1&gt;
&lt;p&gt;Outside of work, all my coding was done with Copilot enabled.
I also had vim configured with rust-analyzer to show errors automatically and give me suggestions.&lt;/p&gt;
&lt;p&gt;This worked pretty well, and it felt immediately productive.
The common wisdom is that you should use the best power tools you can in your editor to be as productive as possible, and I wanted to be productive.
More tools, we're told, will help us be better.&lt;/p&gt;
&lt;p&gt;But then...&lt;/p&gt;
&lt;p&gt;I stopped writing very much code for fun.
It wasn't conscious, but when I sat down at my desk, the last thing I wanted to do was work on personal projects.
Sometimes I made myself and it was enjoyable, but then I'd fall into a funk again.
I just couldn't motivate myself for the projects I was working on.&lt;/p&gt;
&lt;p&gt;There are a lot of factors at play in my life right now.
I told myself that this is because work is a lot, because kids are a lot, because transition is &lt;em&gt;a lot&lt;/em&gt;.
I didn't suspect that my editor and the tools that I setup to &lt;em&gt;help&lt;/em&gt; me were related.&lt;/p&gt;
&lt;h1 id="rediscovering-joy"&gt;Rediscovering joy&lt;/h1&gt;
&lt;p&gt;But last week, a friend at &lt;a href="https://www.recurse.com/"&gt;RC&lt;/a&gt; noted that she doesn't use syntax highlighting and doesn't use other noisy editor plugins.
I've been curious about stopping using syntax highlighting, and her reasoning really spoke to me, so I tried it.&lt;/p&gt;
&lt;p&gt;I went and found a grayscale color scheme (since I do like a &lt;em&gt;small&lt;/em&gt; amount of visual distinction for comments) and installed it.
Immediately, I felt some relief.
I disabled my LSP plugin in vim, disabled rust-analyzer.
More relief.&lt;/p&gt;
&lt;p&gt;When I went to work on a project, suddenly it was... fun again?
The editor was sitting there, waiting for me to enter code.
No code would appear unless I typed it.
The only thoughts entered would be mine.&lt;/p&gt;
&lt;p&gt;This is the way that I fell in love with programming: the editor a channel for my thoughts, the compiler transforming them into something I could run, and little else in the way.&lt;/p&gt;
&lt;p&gt;It has taken me much of adulthood to come to understand how my brain works.
It baffles me that people can take in a lot of visual and auditory noise and still be productive; how anyone can achieve &lt;em&gt;anything&lt;/em&gt; in an open office is beyond me.
This noise extends into the digital: Slack pings, email notifications.
And by using plugins that push information into my editor, I extended it into vim.&lt;/p&gt;
&lt;p&gt;This works for a great many other devs, and I'm glad that we have these tools.
But I don't understand it.
It's not a relatable experience.&lt;/p&gt;
&lt;p&gt;It's like with word processors and spellcheck.
When we used them in school for homework, most of my classmates left spellchecking on, and caught errors as they went or ignored them until they were done, with seeming ease.
I had to disable it.
Each red squiggle under a misspelled word would vaporize my thinly held train of thought, which I then had to claw back.
Some of the time I would remember to run it after my assignment was done; other times, I just got points off.&lt;/p&gt;
&lt;p&gt;It's like that with LSPs that give error checking, and it's like that with Copilot.
When my tooling pushes information into my editor, it vaporizes my concentration.
This is harder to see with Copilot, since the value it gives is in part being able to do more with less focus.
But the end result is that it sucked the joy out of things, because with these tools I could not reach the flow state I am so deeply in love with.
If I got close, an error or suggestion would rip me back out.&lt;/p&gt;
&lt;h1 id="looking-for-equilibrium"&gt;Looking for equilibrium&lt;/h1&gt;
&lt;p&gt;Last week, after that conversation with my friend, I had disabled everything.
No more LSP, no more Copilot, no more colors.
That was great at first, and it was a reactionary response.&lt;/p&gt;
&lt;p&gt;But I think I can strike a better balance.
These tools &lt;em&gt;do&lt;/em&gt; provide value, and my problem isn't with the tools but with my relationship with them.
I was letting them control me and control my interactions with them.&lt;/p&gt;
&lt;p&gt;From now on, I'm in charge.
I'll control those interactions, and the tools will do what I ask them, only &lt;em&gt;when&lt;/em&gt; I ask them to do it.&lt;/p&gt;
&lt;p&gt;I've dipped my toes back into the tool waters.
I re-installed my Copilot plugin, but left it disabled.
There's a hotkey to invoke it when, and only when, I want to reach for a suggestion.&lt;/p&gt;
&lt;p&gt;Sometime I'll add my LSP integrations back in for some more power, without the visual noise.
I like some aspects of them, but I can't deal with others.
Finding equilibrium is hard, and I think it's worth pursuing.&lt;/p&gt;
&lt;p&gt;But only if the joy remains&lt;sup class="footnote-reference" id="fr-2-1"&gt;&lt;a href="https://ntietz.com/blog/changing-my-relationship-with-github-copilot/#fn-2"&gt;[2]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;hr /&gt;&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-1"&gt;
&lt;p&gt;I have never had it enabled in Markdown files or for other prose writing. My words are an expression of my humanity, and I refuse to use LLMs in my writing practice. &lt;a href="https://ntietz.com/blog/changing-my-relationship-with-github-copilot/#fr-1-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-2"&gt;
&lt;p&gt;This is my opinion, not the opinion of any of my former employers. I'm fairly sure that my former employers don't care if joy remains or not, as long as they make money. &lt;a href="https://ntietz.com/blog/changing-my-relationship-with-github-copilot/#fr-2-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 28 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/changing-my-relationship-with-github-copilot/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Quality of life improvements for your Linux system</title><link>https://yusuf.fyi/posts/quality-of-life-linux/</link><description>The little things count towards making a great operating system</description><author>Structs &amp;amp; Company | Yusuf Bouzekri</author><pubDate>Mon, 28 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://yusuf.fyi/posts/quality-of-life-linux/</guid></item><item><title>Importing Activities for a Temporal Workflow in Python</title><link>https://www.danielcorin.com/til/temporal/defining-workflows-in-python/</link><description>Importing Activities for a Temporal Workflow in Python</description><author>Thought Eddies</author><pubDate>Sun, 27 Aug 2023 12:58:08 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/temporal/defining-workflows-in-python/</guid></item><item><title>Wikipedia 3D Embeddings</title><link>https://briansunter.com/projects/wikipedia-3d-embeddings</link><description>Embed thousands of Wikipedia articles, project them into 3D with UMAP, and fly through the result in the browser. Articles cluster by what they're about, not by what folder they live in.</description><author>Brian Sunter</author><pubDate>Sun, 27 Aug 2023 06:06:29 GMT</pubDate><guid isPermaLink="true">https://briansunter.com/projects/wikipedia-3d-embeddings</guid></item><item><title>Project Panthalia: AI-assisted mobile blogging platform</title><link>https://zackproser.com/videos/panthalia-intro</link><description>Panthalia enables me to author rich-media blog posts on the go - assisted by StableDiffusion XL for image generation</description><author>Zachary Proser</author><pubDate>Sun, 27 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/videos/panthalia-intro</guid></item><item><title>Further sqlite-mode extensions</title><link>https://xenodium.com/further-sqlite-mode-extensions</link><description>&lt;p&gt;I've continued poking at Emacs 29's sqlite-mode. Since &lt;a href="https://xenodium.com/sqlite-mode-goodies/"&gt;my last post on extensions&lt;/a&gt;, I've experimented a little with adding a handful of interactive functions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sqlite-mode-extras-compose-and-execute&lt;/code&gt;: Compose and execute a query.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/further-sqlite-mode-extensions/compose-execute.gif" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sqlite-mode-extras-execute&lt;/code&gt;: Execute a query.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/further-sqlite-mode-extensions/execute.gif" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sqlite-mode-extras-add-row&lt;/code&gt;: Add row to table at point.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/further-sqlite-mode-extensions/add-row.gif" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sqlite-mode-extras-delete-row-dwim&lt;/code&gt;: Similar to &lt;code&gt;sqlite-mode-delete&lt;/code&gt; but also enables deleting range in region.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/further-sqlite-mode-extensions/delete-rows.gif" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sqlite-mode-extras-refresh&lt;/code&gt;: Refreshes the buffer re-querying the database.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sqlite-mode-extras-ret-dwim&lt;/code&gt;: If on table, toggle expansion. If on row, edit it.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sqlite-mode-extras-execute-and-display-select-query&lt;/code&gt;: Executes a query and displays results.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/further-sqlite-mode-extensions/select-earth.gif" /&gt;&lt;/p&gt;
&lt;p&gt;I've been playing with the following key bindings:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(use-package sqlite-mode-extras
  :bind (:map
         sqlite-mode-map
         (&amp;quot;n&amp;quot; . next-line)
         (&amp;quot;p&amp;quot; . previous-line)
         (&amp;quot;b&amp;quot; . sqlite-mode-extras-backtab-dwim)
         (&amp;quot;f&amp;quot; . sqlite-mode-extras-tab-dwim)
         (&amp;quot;+&amp;quot; . sqlite-mode-extras-add-row)
         (&amp;quot;D&amp;quot; . sqlite-mode-extras-delete-row-dwim)
         (&amp;quot;C&amp;quot; . sqlite-mode-extras-compose-and-execute)
         (&amp;quot;E&amp;quot; . sqlite-mode-extras-execute)
         (&amp;quot;S&amp;quot; . sqlite-mode-extras-execute-and-display-select-query)
         (&amp;quot;DEL&amp;quot; . sqlite-mode-extras-delete-row-dwim)
         (&amp;quot;g&amp;quot; . sqlite-mode-extras-refresh)
         (&amp;quot;&amp;lt;backtab&amp;gt;&amp;quot; . sqlite-mode-extras-backtab-dwim)
         (&amp;quot;&amp;lt;tab&amp;gt;&amp;quot; . sqlite-mode-extras-tab-dwim)
         (&amp;quot;RET&amp;quot; . sqlite-mode-extras-ret-dwim)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code lives in &lt;a href="https://github.com/xenodium/dotsies/blob/main/emacs/ar/sqlite-mode-extras.el"&gt;sqlite-mode-extras.el&lt;/a&gt; under my &lt;a href="https://github.com/xenodium/dotsies/tree/main"&gt;Emacs config repo&lt;/a&gt;. Beware, it's fairly experimental and hasn't been tested thoroughly.&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Sun, 27 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/further-sqlite-mode-extensions</guid></item><item><title>Code Review Guidelines</title><link>https://vit.baisa.cz/notes/learn/code-review/</link><description>how to do a good code review; resources; notes</description><author>Vít Baisa</author><pubDate>Sun, 27 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://vit.baisa.cz/notes/learn/code-review/</guid></item><item><title>Wherever You Go</title><link>https://foreverliketh.is/blog/wherever-you-go/</link><description>&lt;details&gt;
Image Details &amp; Source
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Rick_and_Morty_(comics)"&gt;Rick and Morty (comics) First run (2015–2020)&lt;/a&gt;: Book 1, Volume 2, No. 7 ~ The Rickfinity Crisis, Part One / Dream a Little Dream of Me&lt;/p&gt;
&lt;p&gt;&amp;ldquo;I&amp;rsquo;ve been to the edge of our universe. Slept underneath stars we can&amp;rsquo;t even see from here. Danced with alien royalty in glittering palaces millions of light years away. But&amp;ndash; But I&amp;rsquo;m standing still.&amp;rdquo;&lt;/p&gt;
&lt;/details&gt;
&lt;hr /&gt;
&lt;p&gt;This page was last modified: &lt;strong&gt;September 6th, 2023&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h5 id="there-you-are"&gt;There You Are &lt;a class="anchor" href="#there-you-are"&gt;#&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;I&amp;rsquo;ve spent a lot of my time with &amp;ldquo;travelers&amp;rdquo;; people who desire to &amp;ldquo;see the world&amp;rdquo;. You probably have as well, reader. Search &amp;ldquo;How many people want to travel?&amp;rdquo; and you&amp;rsquo;ll find crappy articles referencing uncited surveys that &lt;em&gt;&lt;strong&gt;start&lt;/strong&gt;&lt;/em&gt; at ~66% and &lt;a href="https://travelprofessionalnews.com/ninety-five-percent-of-people-want-to-travel-within-next-15-months-according-to-vacation-survey-hosted-by-dream-vacations-cruiseone-and-cruises-inc/"&gt;reach up to ~95%&lt;/a&gt;. But let&amp;rsquo;s not stop at obvious tourism industry propaganda, social media and its psy-ops also undoubtedly &lt;a href="https://www.letsroam.com/explorer/social-media-has-changed-travel/"&gt;have an impact&lt;/a&gt; on peoples desires to visit new places.&lt;/p&gt;
&lt;p&gt;Why do people do it though? Ask and you&amp;rsquo;re likely to get answers such as: experience new things, meet different kinds of people, amass priceless memories, learn about cultures or history, rest and relaxation, &lt;a href="https://www.urbandictionary.com/define.php?term=finding%20yourself"&gt;finding yourself&lt;/a&gt; or &lt;a href="https://www.urbandictionary.com/define.php?term=expand%20my%20horizon"&gt;expanding your horizons&lt;/a&gt;. Other answers that people &lt;em&gt;&lt;strong&gt;don&amp;rsquo;t&lt;/strong&gt;&lt;/em&gt; like to bring up are: faking / chasing clout, flexing, mining content, &lt;a href="https://en.wikipedia.org/wiki/Fear_of_missing_out"&gt;fear of missing out (FOMO)&lt;/a&gt;, self-indulgence, &lt;a href="https://en.wikipedia.org/wiki/Keeping_up_with_the_Joneses"&gt;keeping up with the Joneses&lt;/a&gt; and exploitation.&lt;/p&gt;
&lt;h5 id="lets-check"&gt;Let&amp;rsquo;s Check &lt;a class="anchor" href="#lets-check"&gt;#&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;&lt;a href="https://www.nomadicmatt.com/travel-blogs/travel-privilege/"&gt;Traveling is a privilege of the fortunate.&lt;/a&gt; Those lucky enough to be born in a time such as ours where something like &lt;a href="https://en.wikipedia.org/wiki/Space_tourism"&gt;space tourism&lt;/a&gt; is what might be seen as truly &amp;ldquo;elite&amp;rdquo;. Timing alone is, of course, not enough to be one of these &amp;ldquo;blessed&amp;rdquo; however. Your wealth, health, circumstances, choices / priorities, etc. all play a role in whether or not you are bestowed the opportunity.&lt;/p&gt;
&lt;p&gt;Most do not choose their lot in life. And “making the most of it” can only get some so far. Irrespective, many will &lt;em&gt;&lt;strong&gt;NEVER&lt;/strong&gt;&lt;/em&gt; get the opportunity to travel. Do you think those people incapable of “finding themselves”? Do you think all they&amp;rsquo;ll ever have are worthless memories? Surely they could never &lt;em&gt;&lt;strong&gt;truly&lt;/strong&gt;&lt;/em&gt; learn about history or cultures!&lt;/p&gt;
&lt;p&gt;But let me stop grandstanding for the little guys to take a moment to look at &lt;strong&gt;you&lt;/strong&gt;, devoted traveler. There may be a lot of world to explore, but I bet you ain&amp;rsquo;t got a lot of life to explore it with. Wander wherever you&amp;rsquo;d like, but I ask: How long? How much of your time will you bestow upon a place? Years? Months? &lt;em&gt;&lt;strong&gt;A week?!&lt;/strong&gt;&lt;/em&gt; What world are you &amp;ldquo;seeing&amp;rdquo; then, visitor? Resorts? Tourist attractions? &lt;em&gt;Manufactured&lt;/em&gt;, &lt;strong&gt;commercially-sanitized&lt;/strong&gt;, &lt;em&gt;&lt;strong&gt;state-approved&lt;/strong&gt;&lt;/em&gt; &lt;em&gt;&lt;strong&gt;EXPORTS?!&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h5 id="the-global-perspective"&gt;The Global Perspective &lt;a class="anchor" href="#the-global-perspective"&gt;#&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;Here&amp;rsquo;s a great, short (&amp;lt;5 min) &lt;a href="https://youtu.be/GTiRajt-ZnM"&gt;video about over-tourism&lt;/a&gt; (&lt;a href="https://en.wikipedia.org/wiki/Overtourism"&gt;Wikipedia article&lt;/a&gt;). I recently wrote about how &lt;a href="https://foreverliketh.is/blog/dumb-and-nice/"&gt;&amp;ldquo;everything is a scam&amp;rdquo;&lt;/a&gt;. Tourism is (allegedly) a ~7.7 trillion dollar industry. &lt;a href="https://www.statista.com/statistics/1099933/travel-and-tourism-share-of-gdp/"&gt;It (allegedly) accounts for roughly 10% of the global economy.&lt;/a&gt; Homie, someone desperately wants you to spend your money here, and, allow me to assure you, &amp;ldquo;expanding your horizons&amp;rdquo; is the last thing on their mind.&lt;/p&gt;
&lt;p&gt;I loved this quote from the video:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;Tourists can alter the experience of visiting something such that they ruin the very experience that they&amp;rsquo;ve been trying to have.&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It touches on the ugliness of forcing something; of denying it its freedom. There is a certain kind-of beauty in things when they come of their own accord; naturally, spontaneously&amp;hellip; authentically.&lt;/p&gt;
&lt;p&gt;Reader, it is my belief that you are already on the greatest journey of them all. One that even the saddest of &amp;ldquo;influencers&amp;rdquo; could only dream of aspiring to. It doesn&amp;rsquo;t need a ticket, you&amp;rsquo;ve got exclusive VIP access and it&amp;rsquo;s an experience unlike any other.&lt;/p&gt;
&lt;p&gt;Don&amp;rsquo;t let &lt;em&gt;&lt;strong&gt;nobody&lt;/strong&gt;&lt;/em&gt; tell you how to live it, yo.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Contributors: &lt;a href="https://en.wikipedia.org/wiki/Jason_Mraz"&gt;Jason Mraz&lt;/a&gt; &amp;ndash;&amp;gt; &lt;a href="https://youtu.be/u5WiqJFq2-o"&gt;93 Million Miles&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Dream_a_Little_Dream_of_Me"&gt;Dream a Little Dream of Me&lt;/a&gt; &amp;ndash;&amp;gt; &lt;a href="https://en.wikipedia.org/wiki/Doris_Day"&gt;Doris Day&lt;/a&gt; &amp;ndash;&amp;gt; &lt;a href="https://youtu.be/h7j8wa9sWOE"&gt;Cover Version&lt;/a&gt;, &lt;a href="https://linktr.ee/zacgorman"&gt;Zac Gorman&lt;/a&gt;, &lt;a href="https://www.nomadicmatt.com/about-matt/"&gt;Matt Kepnes&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Jeff_Bezos"&gt;Jeff Bezos&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/The_Atlantic"&gt;The Atlantic&lt;/a&gt; &amp;ndash;&amp;gt; &lt;a href="https://en.wikipedia.org/wiki/Annie_Lowrey"&gt;Annie Lowrey&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This blog post is part of Agora Road Travelogue - Aug [&lt;a href="https://forum.agoraroad.com/index.php?threads/agora-road-travelouge-august-blogging.5966/"&gt;#1&lt;/a&gt; &amp;amp; &lt;a href="https://voicedrew.xyz/wr/august-travelogue.xml"&gt;#2&lt;/a&gt;]. A project attempting to encourage individuals to both write and exercise ownership over their content.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id="comments-section"&gt;Comments Section &lt;a class="anchor" href="#comments-section"&gt;#&lt;/a&gt;&lt;/h2&gt;

&lt;div id="fastcomments-widget"&gt;&lt;/div&gt;</description><author>foreverliketh.is</author><pubDate>Sun, 27 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://foreverliketh.is/blog/wherever-you-go/</guid></item><item><title>Working through Gossip Glomers in Racket</title><link>https://nikhilism.com/post/2023/gossip-glomers-racket/</link><description>Gossip Glomers is a series of distributed systems programming challenges from Fly.io. It uses Maelstrom, a platform for describing test workloads that can run your programs as distributed systems nodes. Maelstrom workloads can provide inputs to these nodes (as if they are arriving over a network), inject delays and partitions and then check that your system still satisfies the invariants of each challenge.
So far I have made it through 3 of the 6 challenges1.</description><author>Nikhil's blog</author><pubDate>Sun, 27 Aug 2023 00:26:28 GMT</pubDate><guid isPermaLink="true">https://nikhilism.com/post/2023/gossip-glomers-racket/</guid></item><item><title>Managing Innovation: How to Lead a Team Through Uncharted Territory</title><link>https://mitrapunk.com/managing-innovation-how-to-lead-a-team-through-uncharted-territory/</link><description>In an era marked by rapid technological advancements, the responsibility for software development managers extends far beyond code reviews and sprint planning</description><author>Mitrapunk: Engineering Game</author><pubDate>Sun, 27 Aug 2023 00:05:27 GMT</pubDate><guid isPermaLink="true">https://mitrapunk.com/managing-innovation-how-to-lead-a-team-through-uncharted-territory/</guid></item><item><title>Disable Obsidian's blinking cursor</title><link>https://www.danielcorin.com/til/obsidian/blinking-cursor/</link><description>A post about disabling the blinking cursor in Obsidian</description><author>Thought Eddies</author><pubDate>Sat, 26 Aug 2023 22:23:00 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/obsidian/blinking-cursor/</guid></item><item><title>Programming as Theory Building</title><link>https://mitrapunk.com/programming-as-theory-building/</link><description>Naur argues that the primary aim of programming should be for programmers to build a comprehensive understanding or "theory" of the problem they are solving</description><author>Mitrapunk: Engineering Game</author><pubDate>Sat, 26 Aug 2023 13:16:55 GMT</pubDate><guid isPermaLink="true">https://mitrapunk.com/programming-as-theory-building/</guid></item><item><title>A few late book reviews</title><link>https://nicolaiarocci.com/a-few-late-book-reviews/</link><description>&lt;p&gt;I&amp;rsquo;ve been reading a few books throughout the summer and needed to be more active in reviewing them here. Rather than
writing five individual posts in a row (too lazy for that), I will catch up with this single post.&lt;/p&gt;
&lt;h2 id="born-to-run-2"&gt;Born to Run 2&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve been back to running after a long hiatus, and this book helped me get back on track with the right, lightly-hearted
approach. The fundamentals are solid (the barefoot-like technique is the way), the 90-day training plan is a good
platform, the nutrition hints are remarkable, and I appreciated the injury-treatment segments. There&amp;rsquo;s too much chitchat
for my liking, though, with many stories, anecdotes, and non-technical, gospel-like content. Some chapters are
unattractive to the experienced runner (running with dogs, the music while running debate, and training with scooters?).
This book targets the newcomer and the veteran runner switching to the barefoot technique. The first book in the series,
Born to Run, was the commercial hit introducing barefoot running to the masses. This one is trying to be both a sequel
and something different that can live independently. Meeting all these goals was a complicated bet. &lt;em&gt;Authors: Chris
McDougall, Eric Orton.&lt;/em&gt;&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Sat, 26 Aug 2023 09:05:25 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/a-few-late-book-reviews/</guid></item><item><title>Tacit Knowledge is Dangerous</title><link>https://er4hn.info/blog/2023.08.26-tacit-knowledge-dangerous/</link><description>Then Life Continued</description><author>er4hn</author><pubDate>Sat, 26 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://er4hn.info/blog/2023.08.26-tacit-knowledge-dangerous/</guid></item><item><title>Hit Refresh: The Quest to Rediscover Microsoft’s Soul and Imagine a Better Future for Everyone</title><link>https://olshansky.info/book/hit_refresh/</link><description>Olshansky's review of Hit Refresh</description><author>🦉 olshansky 🦁</author><pubDate>Sat, 26 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/book/hit_refresh/</guid></item><item><title>Three nights as a tourist in London</title><link>https://www.planetjones.net/blog/26-08-2023/three-nights-in-london.html</link><description>Three night trip review of London - including afternoon tea at Claridges, Buckingham palace summer opening, Chinatown dumplings and a great boat trip down the Thames with the Thames Clipper / Uber boat.</description><author>Jonathan Jones homepage: planetjones.net</author><pubDate>Sat, 26 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.planetjones.net/blog/26-08-2023/three-nights-in-london.html</guid></item><item><title>DataEngBytes 2023</title><link>https://boyter.org/posts/dataengbytes-2023/</link><description>&lt;p&gt;I was recently selected to present at &lt;a href="https://dataengconf.com.au/"&gt;DataEngBytes 2023&lt;/a&gt; after submitting an idea for a talk based on my blog post &lt;a href="https://boyter.org/posts/an-informal-survey-of-10-million-github-bitbucket-gitlab-projects/"&gt;Processing 40 TB of code from ~10 million projects with a dedicated server and Go for $100&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I think it went well, other than not having the slides able to present for the first 10 minutes. After 5 mins or so I decided to just plough on since it was less of a visual talk anyway. I did have a few people come to me afterwards and thank me for it which was nice.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Sat, 26 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/dataengbytes-2023/</guid></item><item><title>My Top 10 Reads of the Month</title><link>https://marcolabarile.me/news/2023/08/25/my-top-10-reads-of-the-month/</link><description>Have a look at the best articles, posts, projects and stories I've found this month. Featured topics: programming, science, security, AI.</description><author>MARCO LABARILE</author><pubDate>Sat, 26 Aug 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://marcolabarile.me/news/2023/08/25/my-top-10-reads-of-the-month/</guid></item><item><title>How to create a recursive type in TypeScript</title><link>https://muratov.space/blog/how-to-create-recursive-type-in-typescript/</link><description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-typescript"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #ff79c6;"&gt;type&lt;/span&gt; RecursiveTypeNode&amp;lt;&lt;span style="color: #ff79c6;"&gt;T&lt;/span&gt;&amp;gt; &lt;span style="color: #ff79c6;"&gt;=&lt;/span&gt; Record&amp;lt;&lt;span style="color: #ff79c6;"&gt;string&lt;/span&gt;, &lt;span style="color: #50fa7b;"&gt;T&lt;/span&gt; | &lt;span style="color: #50fa7b;"&gt;null&lt;/span&gt;&amp;gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #ff79c6;"&gt;interface&lt;/span&gt; RecursieveType &lt;span style="color: #ff79c6;"&gt;extends&lt;/span&gt; RecursiveTypeNode&amp;lt;&lt;span style="color: #ff79c6;"&gt;RecursieveType&lt;/span&gt;&amp;gt; {}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I needed to create a type for a recursive structure like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-typescript"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #ff79c6;"&gt;const&lt;/span&gt; template &lt;span style="color: #ff79c6;"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt; &lt;span style="color: #f1fa8c;"&gt;'Page 1'&lt;/span&gt;&lt;span style="color: #ff79c6;"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt; &lt;span style="color: #f1fa8c;"&gt;'Page 1 1'&lt;/span&gt;&lt;span style="color: #ff79c6;"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt; &lt;span style="color: #f1fa8c;"&gt;'Page 1 1 1'&lt;/span&gt;&lt;span style="color: #ff79c6;"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt; &lt;span style="color: #f1fa8c;"&gt;'Page 1 1 1 1'&lt;/span&gt;&lt;span style="color: #ff79c6;"&gt;:&lt;/span&gt; &lt;span style="color: #ff79c6;"&gt;null&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt; &lt;span style="color: #f1fa8c;"&gt;'Page 1 1 1 2'&lt;/span&gt;&lt;span style="color: #ff79c6;"&gt;:&lt;/span&gt; &lt;span style="color: #ff79c6;"&gt;null&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt; &lt;span style="color: #f1fa8c;"&gt;'Page 1 1 2'&lt;/span&gt;&lt;span style="color: #ff79c6;"&gt;:&lt;/span&gt; &lt;span style="color: #ff79c6;"&gt;null&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt; &lt;span style="color: #f1fa8c;"&gt;'Page 1 2'&lt;/span&gt;&lt;span style="color: #ff79c6;"&gt;:&lt;/span&gt; &lt;span style="color: #ff79c6;"&gt;null&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt; &lt;span style="color: #f1fa8c;"&gt;'Page 1 3'&lt;/span&gt;&lt;span style="color: #ff79c6;"&gt;:&lt;/span&gt; &lt;span style="color: #ff79c6;"&gt;null&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt; &lt;span style="color: #f1fa8c;"&gt;'Page 2'&lt;/span&gt;&lt;span style="color: #ff79c6;"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt; &lt;span style="color: #f1fa8c;"&gt;'Page 2 1'&lt;/span&gt;&lt;span style="color: #ff79c6;"&gt;:&lt;/span&gt; &lt;span style="color: #ff79c6;"&gt;null&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt; &lt;span style="color: #f1fa8c;"&gt;'Page 2 2'&lt;/span&gt;&lt;span style="color: #ff79c6;"&gt;:&lt;/span&gt; &lt;span style="color: #ff79c6;"&gt;null&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt; &lt;span style="color: #f1fa8c;"&gt;'Page 3'&lt;/span&gt;&lt;span style="color: #ff79c6;"&gt;:&lt;/span&gt; &lt;span style="color: #ff79c6;"&gt;null&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is a collection of pages; every page is either a collection of pages or null. My first thought was to simply write this:&lt;/p&gt;</description><author>muratov.space</author><pubDate>Fri, 25 Aug 2023 23:00:00 GMT</pubDate><guid isPermaLink="true">https://muratov.space/blog/how-to-create-recursive-type-in-typescript/</guid></item><item><title>A Bit of SaaS Weekly: Squeeze Simplicity</title><link>https://ethanmick.com/a-bit-of-saas-weekly-squeeze-simplicity/</link><description>&lt;p&gt;This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by &lt;a href="https://ethanmick.com/"&gt;Ethan Mick&lt;/a&gt;.&lt;/p&gt;&lt;blockquote&gt;Friends don&amp;#x2019;t let friends pick us-east-1.&lt;br /&gt;&lt;br /&gt;- AWS Proverbs&lt;/blockquote&gt;&lt;p&gt;That is all. Continue along your US East 2 day.&lt;/p&gt;&lt;hr /&gt;&lt;h2 id="the-best-bits"&gt;The Best Bits&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="https://ai.meta.com/blog/code-llama-large-language-model-coding/?ref=ethanmick.com"&gt;Code Llama 2&lt;/a&gt;, an open-&lt;/li&gt;&lt;/ul&gt;</description><author>Build SaaS with Ethan</author><pubDate>Fri, 25 Aug 2023 13:00:19 GMT</pubDate><guid isPermaLink="true">https://ethanmick.com/a-bit-of-saas-weekly-squeeze-simplicity/</guid></item><item><title>The Photo Game: Matrimony</title><link>https://andrewbridges.org/the-photo-game-matrimony/</link><description>I went to a friend's wedding. It was a western-themed event with burros, llamas, a horse-drawn carriage, and an old-timey prospector.</description><author>Show Notes</author><pubDate>Fri, 25 Aug 2023 04:26:24 GMT</pubDate><guid isPermaLink="true">https://andrewbridges.org/the-photo-game-matrimony/</guid></item><item><title>Add a favicon to your Rails app in 2 minutes</title><link>https://railsnotes.xyz/blog/rails-add-favicon-2-minutes</link><description>This is a super short article on how to add a favicon to your Ruby on Rails app. We combine the favicon_link_tag helper with asset_path, which makes it quick and easy.</description><author>RailsNotes, the Ruby on Rails guides you wished you had.</author><pubDate>Fri, 25 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://railsnotes.xyz/blog/rails-add-favicon-2-minutes</guid></item><item><title>ChatGPT and the Future of Technical Interviews</title><link>https://prashamhtrivedi.in/post/interview_chatgpt/</link><description>The age of AI and LLMs are upon us already. And it&amp;rsquo;s changing many aspects of life. Making most of the things easy, while making some things obsolate. But one thing is sure, things are not going to be same moving forward.
In the age of AI and large language models like ChatGPT, the way we conduct interviews is changing. Traditional theoretical knowledge-based questions may not be as effective in assessing a candidate&amp;rsquo;s true potential (I always believe it serves a limited purpose).</description><author>Prasham H Trivedi</author><pubDate>Fri, 25 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://prashamhtrivedi.in/post/interview_chatgpt/</guid></item><item><title>Other people's code</title><link>https://holzer.online/weeknotes/2023/34/index.html</link><description>&lt;p&gt;
Hillel Wayne wrote about &lt;a href="https://buttondown.email/hillelwayne/archive/educational-codebases/"&gt;educational codebases&lt;/a&gt;. I find the idea quite interesting, and have been trying to gather some further thoughts into an article, but while writing it, my doubts grew on how feasible the idea beyond a certain threshold of complexity really is. While Hillel demos the idea with small, but extensively commented scripts, I was looking for programs, that are still comprehensible for a single person, but tackle a larger problem/domain. You can find a few examples, especially for system software (operating systems and compilers), that are not for production but  not toy programs as well, that are either made for teaching purposes or would at least lend themself well for study. But my current hunch is that this genre will not see significant numbers, for two reasons: it is rather hard to make and there is less demand for an analytical approach of breaking an existing system down into pieces then for for bottom-up and cookbook style material.
&lt;/p&gt;

&lt;time datetime="2023-08-25" title="That's 2023-08-25 for Non-Discordians"&gt;
Boomtime, the 18th day of Bureaucracy in the YOLD 3189
&lt;/time&gt;</description><author>Fabian's public notepad</author><pubDate>Fri, 25 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://holzer.online/weeknotes/2023/34/index.html</guid></item><item><title>Better be first 99% of the time than second 100% of the time</title><link>https://bytepawn.com/better-be-first-99-percent-of-the-time-than-second-100-percent-of-the-time.html</link><description>&lt;p&gt;A review of the Donald MacKenzie's book &lt;em&gt;Trading at the Speed of Light&lt;/em&gt;, which gives an excellent history and inside-peek of the world of High Frequency Trading, or HFT.&lt;br /&gt;&lt;br /&gt; &lt;img alt="Trading at the Speed of Light" src="/images/trading-at-the-speed-of-light.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Fri, 25 Aug 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/better-be-first-99-percent-of-the-time-than-second-100-percent-of-the-time.html</guid></item><item><title>The Great Desolation: Thoughts on Mastodon and BlueSky</title><link>https://bastian.rieck.me/blog/2023/social/</link><description>&lt;p&gt;I always had maintained a strained relationship with &amp;rsquo;the social network
formerly known as Twitter,&amp;rsquo; i.e. &amp;lsquo;X.&amp;rsquo; On the one hand, I had
a semi-curated newsfeed, on the other hand, I started seeing the allure
of more and more posturing. When conference deadlines rolled around,
I was not always looking forward to the many posts of the form &amp;lsquo;So
grateful that my lab has &lt;code&gt;$INSANE_NUMBER_OF_PAPERS&lt;/code&gt; accepted at
&lt;code&gt;$AWESOME_CONFERENCE&lt;/code&gt;. Nevertheless, until recently, &amp;lsquo;X&amp;rsquo; still provided
a lot of value to me, but the recent changes in management, accompanied
by a general increase in instability, made me look for alternatives.&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;I am particularly interested in services that aim to provide
a &lt;em&gt;federated network&lt;/em&gt;, since the silo or &amp;lsquo;walled garden&amp;rsquo; mentality of
the early 2000s opposes the fundamental principles of the internet, viz.
that protocols should be interoperable to ensure participation by the
largest number of people.&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Since Threads is not available to European users yet, I can only offer
some sparse insights into two services, Mastodon and BlueSky, which
occupy very different points in the landscape of social networks.&lt;/p&gt;
&lt;h1 id="mastodon"&gt;Mastodon&lt;/h1&gt;
&lt;p&gt;Mastodon is &lt;em&gt;the&lt;/em&gt; foremost platform for decentralised
social networks. Anyone can run a service&amp;mdash;an instance&amp;mdash;and
communicate with others. I created my usual handle &lt;code&gt;Pseudomanifold&lt;/code&gt; at
an instance called &lt;a href="https://mathstodon.xyz"&gt;Mathstodon.xyz&lt;/a&gt;, which offers
a save haven to everyone interested in mathematics. I am now known as
&lt;a href="https://mathstodon.xyz/@Pseudomanifold"&gt;&lt;code&gt;@Pseudomanifold@mathstodon.xyz&lt;/code&gt;&lt;/a&gt;,
but the great thing is that I could &lt;em&gt;switch&lt;/em&gt; instances if I need to.&lt;sup id="fnref:3"&gt;&lt;a class="footnote-ref" href="#fn:3"&gt;3&lt;/a&gt;&lt;/sup&gt;
While my interactions on Mastodon have been pleasant so far, it all
feels a little bit smaller than &amp;lsquo;X.&amp;rsquo; Maybe that is a good thing for the
long run? In terms of users experience, I like the idea of
being&amp;mdash;theoretically&amp;mdash;able to run my own server any case; way back
when, I ran an &lt;a href="https://elgg.org/"&gt;elgg&lt;/a&gt; instance for a couple of
friends, but it took never off. However, the overall process of
following people from other instances still feels clunky to me; I guess
that is an inevitable drawback of federation.&lt;/p&gt;
&lt;h1 id="bluesky"&gt;BlueSky&lt;/h1&gt;
&lt;p&gt;Thanks to &lt;a href="https://github.com/dmarx"&gt;David&lt;/a&gt;, I got an
invite to this network. BlueSky aims to forge its own path by pitching
the &lt;a href="https://atproto.com/"&gt;AT Protocol&lt;/a&gt;, yet another standard to allow
federation, but wholly incompatible with Mastodon&amp;rsquo;s
&lt;a href="https://en.wikipedia.org/wiki/ActivityPub"&gt;ActivityPub&lt;/a&gt; protocol.
I cannot say that I really delved into the technical details of that
protocol, but what I dig about it is that you get to create your own
verified handles by either using &lt;code&gt;DNS TXT&lt;/code&gt; records or a web server with
a &lt;code&gt;.well-known&lt;/code&gt; route. That speaks to the inner nerd of me, so of course
I had to fiddle with this, and I am now &lt;code&gt;@Pseudomanifold.topology.rocks&lt;/code&gt;
on that platform:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;% dig -t TXT _atproto.pseudomanifold.topology.rocks

; &amp;lt;&amp;lt;&amp;gt;&amp;gt; DiG 9.18.18 &amp;lt;&amp;lt;&amp;gt;&amp;gt; -t TXT _atproto.pseudomanifold.topology.rocks
;; global options: +cmd
;; Got answer:
;; -&amp;gt;&amp;gt;HEADER&amp;lt;&amp;lt;- opcode: QUERY, status: NOERROR, id: 48713
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;_atproto.pseudomanifold.topology.rocks.        IN TXT

;; ANSWER SECTION:
_atproto.pseudomanifold.topology.rocks. 10 IN TXT "did=did:plc:iyzbg5xvdcdtquruxwofbczn"
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As far as I understand, the handle &amp;lsquo;belongs&amp;rsquo; to me, and I could take it
with me to another BlueSky instance, provided there are any in the
future. That is very neat but also fully captures my interaction with the
network so far; like Mastodon, it feels slower and empty. Maybe I missed
out on the original exodus?&lt;/p&gt;
&lt;h1 id="thoughts"&gt;Thoughts&lt;/h1&gt;
&lt;p&gt;I am not enthralled by any of these options, including X. Having to
choose between whimsical admins and empty timelines is a choice between
&lt;a href="https://en.wikipedia.org/wiki/Between_Scylla_and_Charybdis"&gt;Scylla and Charybdis&lt;/a&gt;.
As X is starting to slowly wind down, at least when it comes to academic
content, I am not sure what&amp;mdash;if anything&amp;mdash;will replace it. If I had my
say, I would love to have a multi-protocol app, like the venerable &lt;a href="https://miranda-ng.org/"&gt;Miranda NG&lt;/a&gt; that spanned different protocols. That way, I could post and read from a single source, without having to juggle multiple apps.&lt;/p&gt;
&lt;p&gt;I am glad that we developed e-mail well before these social networks;
otherwise, I would have to visit 5 different websites to communicate
with people. If the future is decentralised, I hope it will make it easy
for people to participate.&lt;/p&gt;
&lt;p&gt;(Feel free to follow me via &lt;code&gt;@Pseudomanifold@mathstodon.xyz&lt;/code&gt; on Mastodon or
&lt;code&gt;@Pseudomanifold.topology.rocks&lt;/code&gt; on BlueSky; I would love to have more
activity in my timeline)&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;I still maintain my presence on X for the time being.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;I also admit to having doubts about my content being &amp;lsquo;owned&amp;rsquo; by
someone else, but the ephemeral nature of services like X makes
that worry somewhat moot.&amp;#160;&lt;a class="footnote-backref" href="#fnref:2"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;Although in practice, some things like transferring followers and
so on might not work out because of the decentralised nature. There
are also, as far as I can tell, no safeguards against rogue admins,
meaning that one still has to trust the admins. Since this is
essentially the same problem as with other services, I do not see it
as a large drawback.&amp;#160;&lt;a class="footnote-backref" href="#fnref:3"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>Ecce Homology on Bastian Grossenbacher Rieck's personal homepage</author><pubDate>Thu, 24 Aug 2023 23:00:00 GMT</pubDate><guid isPermaLink="true">https://bastian.rieck.me/blog/2023/social/</guid></item><item><title>The Engineering of Curiosity</title><link>https://mitrapunk.com/the-engineering-of-curiosity/</link><description>As an engineer, one of the most exhilarating feelings is being driven by curiosity—a compelling urge to dissect problems, assemble solutions</description><author>Mitrapunk: Engineering Game</author><pubDate>Thu, 24 Aug 2023 19:56:06 GMT</pubDate><guid isPermaLink="true">https://mitrapunk.com/the-engineering-of-curiosity/</guid></item><item><title>Godot Wild Jam 60</title><link>https://evilcookie.de/godot-wild-jam-60.html</link><description/><author>blog</author><pubDate>Thu, 24 Aug 2023 16:26:01 GMT</pubDate><guid isPermaLink="true">https://evilcookie.de/godot-wild-jam-60.html</guid></item><item><title>A 3D Game of Life</title><link>https://blog.lukesalamone.com/posts/game-of-life-3d/</link><description>&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life"&gt;Conway&amp;rsquo;s Game of Life&lt;/a&gt; is a simulation developed in 1970 describing a grid of binary cells and transition rules for each cell which depend on the state of the cell&amp;rsquo;s neighbors. It&amp;rsquo;s capable of creating some pretty cool patterns.&lt;/p&gt;
&lt;p&gt;This variant of the Game of Life uses three overlapping channels, so instead of just one simulation, there are three simultaneous simulations. I visualize these in the three color channels, red, green and blue. Two or more channels active on the same cell are represented with &lt;a href="https://en.wikipedia.org/wiki/Additive_color"&gt;additive color mixing&lt;/a&gt;.&lt;/p&gt;</description><author>Luke Salamone's Blog</author><pubDate>Thu, 24 Aug 2023 09:34:38 GMT</pubDate><guid isPermaLink="true">https://blog.lukesalamone.com/posts/game-of-life-3d/</guid></item><item><title>Evaluating Algebraic Expressions using Parser Combinators</title><link>https://blog.varunramesh.net/posts/algebraic-expressions-using-parser-combinators/</link><description>In this post, I describe how parser combinators can be used to parse recursive languages, as well as deal with operator precedence and associativity.</description><author>Varun Ramesh's Blog</author><pubDate>Thu, 24 Aug 2023 09:25:17 GMT</pubDate><guid isPermaLink="true">https://blog.varunramesh.net/posts/algebraic-expressions-using-parser-combinators/</guid></item><item><title>Streaming Convolutions</title><link>https://ben.bolte.cc/posts/2023-08-24-streaming-convs</link><description>Working out the math for streaming convolutions.</description><author>Ben Bolte</author><pubDate>Thu, 24 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ben.bolte.cc/posts/2023-08-24-streaming-convs</guid></item><item><title>Searching for the Perfect Keyboard</title><link>https://thoughts.greyh.at/posts/perfect-keyboard/</link><description>For years, I&amp;rsquo;ve been using a CODE Keyboard equipped with Cherry MX Clear switches. It has served me faithfully, but recently, I&amp;rsquo;ve felt the need to try something different. Given the plethora of options now available, I embarked on a quest to find the perfect keyboard.</description><author>Terminal Thoughts</author><pubDate>Thu, 24 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://thoughts.greyh.at/posts/perfect-keyboard/</guid></item><item><title>Setting up CUDA on WSL2 in 2023</title><link>https://rachitsingh.com/notes/wsl-cuda/</link><description>&lt;p&gt;Setting up CUDA on WSL2 probably costs you a little bit of performance but gives you a lot of flexibility - you're essentially running a VM with (some) Microsoft support. Here's the best source: &lt;a href="https://learn.microsoft.com/en-us/windows/ai/directml/gpu-cuda-in-wsl"&gt;Enable NVIDIA CUDA in WSL&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Luckily in the past year or so NVIDIA has started supporting this path, so you're (kind of) in good hands. A lot of advice will be mixed between Ubuntu and WSL, though, so I'm writing down my experience with installing CUDA on WSL2 today.&lt;/p&gt;
&lt;p&gt;The below is a bit of a bloated install, so you might be able to get away with fewer things (for example TensorRT is pretty optional).&lt;/p&gt;
&lt;h3 id="install-drivers"&gt;Install drivers&lt;/h3&gt;
&lt;p&gt;First, you'll need to get an NVIDIA driver (in Windows) for your card. I got the "NVIDIA Game Ready" driver from NVIDIA's website directly. Note that this is &lt;em&gt;not&lt;/em&gt; installing an NVIDIA driver for your WSL (i.e. Linux) installation. As far as I can tell, this process uses your Windows driver directly (which makes sense, having two drivers for the same physical device running seems like a recipe for disaster).&lt;/p&gt;
&lt;p&gt;I chose:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No GEForce Experience&lt;/li&gt;
&lt;li&gt;HD Audio Driver&lt;/li&gt;
&lt;li&gt;PhysX&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;though of course you can make other choices. Once this is finished, you will see a bunch of files in &lt;code&gt;C:\Windows\System32\lxss\lib\&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #011627; color: #d6deeb;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #addb67;"&gt;~&lt;/span&gt;&lt;span&gt; ❯ ls&lt;/span&gt;&lt;span style="color: #d7dbe0;"&gt; -l&lt;/span&gt;&lt;span&gt; /mnt/c/Windows/System32/lxss/lib/
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;-r-xr-xr-x&lt;/span&gt;&lt;span&gt; 1 singhrac singhrac 10188744 Aug 15 00:23 libcudadebugger.so.1
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;-r-xr-xr-x&lt;/span&gt;&lt;span&gt; 1 singhrac singhrac   154088 Aug 15 00:23 libcuda.so
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;-r-xr-xr-x&lt;/span&gt;&lt;span&gt; 1 singhrac singhrac   154088 Aug 15 00:23 libcuda.so.1
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;-r-xr-xr-x&lt;/span&gt;&lt;span&gt; 1 singhrac singhrac   154088 Aug 15 00:23 libcuda.so.1.1
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;-r-xr-xr-x&lt;/span&gt;&lt;span&gt; 3 singhrac singhrac  5401440 Aug 11 11:05 libd3d12core.so
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;-r-xr-xr-x&lt;/span&gt;&lt;span&gt; 3 singhrac singhrac   800296 Aug 11 11:05 libd3d12.so
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;-r-xr-xr-x&lt;/span&gt;&lt;span&gt; 2 singhrac singhrac   827904 Jun  5  2021 libdxcore.so
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;-r-xr-xr-x&lt;/span&gt;&lt;span&gt; 1 singhrac singhrac 10820792 Aug 15 00:23 libnvcuvid.so
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;-r-xr-xr-x&lt;/span&gt;&lt;span&gt; 1 singhrac singhrac 10820792 Aug 15 00:23 libnvcuvid.so.1
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;-r-xr-xr-x&lt;/span&gt;&lt;span&gt; 1 singhrac singhrac 37086552 Aug 15 00:23 libnvdxdlkernels.so
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;-r-xr-xr-x&lt;/span&gt;&lt;span&gt; 1 singhrac singhrac   551528 Aug 15 00:23 libnvidia-encode.so
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;-r-xr-xr-x&lt;/span&gt;&lt;span&gt; 1 singhrac singhrac   551528 Aug 15 00:23 libnvidia-encode.so.1
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;-r-xr-xr-x&lt;/span&gt;&lt;span&gt; 1 singhrac singhrac   233832 Aug 15 00:23 libnvidia-ml.so.1
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;-r-xr-xr-x&lt;/span&gt;&lt;span&gt; 1 singhrac singhrac   362960 Aug 15 00:23 libnvidia-opticalflow.so
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;-r-xr-xr-x&lt;/span&gt;&lt;span&gt; 1 singhrac singhrac   362960 Aug 15 00:23 libnvidia-opticalflow.so.1
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;-r-xr-xr-x&lt;/span&gt;&lt;span&gt; 1 singhrac singhrac    68560 Aug 15 00:23 libnvoptix.so.1
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;-r-xr-xr-x&lt;/span&gt;&lt;span&gt; 1 singhrac singhrac 83265296 Aug 15 00:23 libnvwgf2umx.so
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;-r-xr-xr-x&lt;/span&gt;&lt;span&gt; 1 singhrac singhrac   678064 Aug 15 00:23 nvidia-smi
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and the same set of files in &lt;code&gt;/usr/lib/wsl/lib/&lt;/code&gt;. NOTE! This is the location that the CUDA Runtime will live. However, you also need the CUDA Toolkit (probably). Note that &lt;code&gt;nvidia-smi&lt;/code&gt; will now work if you add &lt;code&gt;/usr/lib/wsl/lib/&lt;/code&gt; to your path:&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #011627; color: #d6deeb;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #addb67;"&gt;~&lt;/span&gt;&lt;span&gt; ❯ nvidia-smi
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;Thu&lt;/span&gt;&lt;span&gt; Aug 24 11:17:06 2023
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;+---------------------------------------------------------------------------------------+
&lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;| &lt;/span&gt;&lt;span style="color: #addb67;"&gt;NVIDIA-SMI&lt;/span&gt;&lt;span&gt; 535.103                Driver Version: 537.13       CUDA Version: 12.2     &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;|
&lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;|&lt;/span&gt;&lt;span style="color: #addb67;"&gt;-----------------------------------------+----------------------+----------------------+
&lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;| &lt;/span&gt;&lt;span style="color: #addb67;"&gt;GPU&lt;/span&gt;&lt;span&gt;  Name                 Persistence-M &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;| &lt;/span&gt;&lt;span style="color: #addb67;"&gt;Bus-Id&lt;/span&gt;&lt;span&gt;        Disp.A &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;| &lt;/span&gt;&lt;span style="color: #addb67;"&gt;Volatile&lt;/span&gt;&lt;span&gt; Uncorr. ECC &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;|
&lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;| &lt;/span&gt;&lt;span style="color: #addb67;"&gt;Fan&lt;/span&gt;&lt;span&gt;  Temp   Perf          Pwr:Usage/Cap &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;|         &lt;/span&gt;&lt;span style="color: #addb67;"&gt;Memory-Usage &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;| &lt;/span&gt;&lt;span style="color: #addb67;"&gt;GPU-Util&lt;/span&gt;&lt;span&gt;  Compute M. &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;|
&lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;|                                         |                      |               &lt;/span&gt;&lt;span style="color: #addb67;"&gt;MIG&lt;/span&gt;&lt;span&gt; M. &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;|
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;|=========================================+======================+======================&lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;|
&lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;|   &lt;/span&gt;&lt;span style="color: #addb67;"&gt;0&lt;/span&gt;&lt;span&gt;  NVIDIA GeForce RTX 2070        On  &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;| &lt;/span&gt;&lt;span style="color: #addb67;"&gt;00000000:07:00.0&lt;/span&gt;&lt;span&gt;  On &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;|                  &lt;/span&gt;&lt;span style="color: #addb67;"&gt;N/A &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;|
&lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;|  &lt;/span&gt;&lt;span style="color: #addb67;"&gt;0%&lt;/span&gt;&lt;span&gt;   51C    P5              20W / 175W &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;|    &lt;/span&gt;&lt;span style="color: #addb67;"&gt;573MiB&lt;/span&gt;&lt;span&gt; /  8192MiB &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;|      &lt;/span&gt;&lt;span style="color: #addb67;"&gt;0%&lt;/span&gt;&lt;span&gt;      Default &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;|
&lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;|                                         |                      |                  &lt;/span&gt;&lt;span style="color: #addb67;"&gt;N/A &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;|
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;+-----------------------------------------+----------------------+----------------------+
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;+---------------------------------------------------------------------------------------+
&lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;| &lt;/span&gt;&lt;span style="color: #addb67;"&gt;Processes:                                                                            &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;|
&lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;|  &lt;/span&gt;&lt;span style="color: #addb67;"&gt;GPU&lt;/span&gt;&lt;span&gt;   GI   CI        PID   Type   Process name                            GPU Memory &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;|
&lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;|        &lt;/span&gt;&lt;span style="color: #addb67;"&gt;ID&lt;/span&gt;&lt;span&gt;   ID                                                             Usage      &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;|
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;|=======================================================================================&lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;|
&lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;|  &lt;/span&gt;&lt;span style="color: #addb67;"&gt;No&lt;/span&gt;&lt;span&gt; running processes found                                                           &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;|
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;+---------------------------------------------------------------------------------------+
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However the "CUDA Version" at the top is the &lt;em&gt;max&lt;/em&gt; supported CUDA Toolkit version that you can use&lt;sup class="footnote-reference" id="fr-1-1"&gt;&lt;a href="https://rachitsingh.com/notes/wsl-cuda/#fn-1"&gt;1&lt;/a&gt;&lt;/sup&gt;. At this point in time (20230824) PyTorch doesn't &lt;em&gt;really&lt;/em&gt; support CUDA 12.2, so it might be a huge pain in the ass if you use that. I recommend 11.8, the currently max supported version on the install page. I'm sure this will change shortly.&lt;/p&gt;
&lt;p&gt;At this point, you probably want to add &lt;code&gt;/usr/lib/wsl/lib/&lt;/code&gt; to your &lt;code&gt;LD_LIBRARY_PATH&lt;/code&gt;, which will let programs find the &lt;code&gt;.so&lt;/code&gt; files there. Put this in your &lt;code&gt;~/.bashrc&lt;/code&gt; or &lt;code&gt;~/.zshrc&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #011627; color: #d6deeb;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #c792ea;"&gt;export &lt;/span&gt;&lt;span style="color: #addb67;"&gt;LD_LIBRARY_PATH&lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;=&lt;/span&gt;&lt;span style="color: #addb67;"&gt;/usr/lib/wsl/lib:$LD_LIBRARY_PATH
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you run &lt;code&gt;ldconfig -p | grep libcuda&lt;/code&gt;, this will now show those libraries (you might have to &lt;code&gt;exec zsh&lt;/code&gt; or restart your shell).&lt;/p&gt;
&lt;h3 id="install-cuda-toolkit"&gt;Install CUDA Toolkit&lt;/h3&gt;
&lt;p&gt;It is really important to listen to &lt;a href="https://docs.nvidia.com/cuda/wsl-user-guide/index.html#cuda-support-for-wsl-2"&gt;NVIDIA's instructions here&lt;/a&gt;: &lt;em&gt;the default CUDA Toolkit comes with a driver, which will overwrite the above set (i.e. piggy-backing off Windows driver)&lt;/em&gt;. So it's important to install the WSL-specific CUDA Toolkit, which doesn't have that driver. This is also where it's really easy to install the latest CUDA Toolkit (i.e. 12.2), which &lt;em&gt;might&lt;/em&gt; cause issues for you. I didn't run into too many the first time, but I think it's easier just to install the older toolkit.&lt;/p&gt;
&lt;p&gt;Go here: &lt;a href="https://developer.nvidia.com/cuda-11-8-0-download-archive?target_os=Linux&amp;amp;target_arch=x86_64&amp;amp;Distribution=WSL-Ubuntu&amp;amp;target_version=2.0&amp;amp;target_type=deb_network"&gt;CUDA Toolkit 11.8&lt;/a&gt;. You can find other versions in the "Archive of Previous CUDA Releases" link.&lt;/p&gt;
&lt;p&gt;You want to choose: Linux &amp;gt; x86_64 &amp;gt; WSL-Ubuntu &amp;gt; 2.0 &amp;gt; (your choice of installer type)&lt;/p&gt;
&lt;p&gt;I personally find it easier to deal with &lt;code&gt;deb&lt;/code&gt; installation because then things like &lt;code&gt;dpkg&lt;/code&gt; will help you manage the installation, but everyone's experience is different. In any case, you want to follow those instructions, making sure you have &lt;code&gt;WSL-Ubuntu&lt;/code&gt; selected. If you use the &lt;code&gt;deb (network)&lt;/code&gt; install, it might try to install 12.2 because that's the most recent version. I think you can work around this by doing &lt;code&gt;sudo apt-get install cuda-toolkit-11-8&lt;/code&gt;, but I used the &lt;code&gt;deb (local)&lt;/code&gt; version instead.&lt;/p&gt;
&lt;p&gt;I personally ran:&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #011627; color: #d6deeb;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #addb67;"&gt;~&lt;/span&gt;&lt;span&gt; ❯ wget https://developer.download.nvidia.com/compute/cuda/repos/wsl-ubuntu/x86_64/cuda-wsl-ubuntu.pin
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;~&lt;/span&gt;&lt;span&gt; ❯ sudo mv cuda-wsl-ubuntu.pin /etc/apt/preferences.d/cuda-repository-pin-600
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;~&lt;/span&gt;&lt;span&gt; ❯ wget https://developer.download.nvidia.com/compute/cuda/11.8.0/local_installers/cuda-repo-wsl-ubuntu-11-8-local_11.8.0-1_amd64.deb
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;~&lt;/span&gt;&lt;span&gt; ❯ sudo dpkg&lt;/span&gt;&lt;span style="color: #d7dbe0;"&gt; -i&lt;/span&gt;&lt;span&gt; cuda-repo-wsl-ubuntu-11-8-local_11.8.0-1_amd64.deb
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;~&lt;/span&gt;&lt;span&gt; ❯ sudo cp /var/cuda-repo-wsl-ubuntu-11-8-local/cuda-&lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;*&lt;/span&gt;&lt;span&gt;-keyring.gpg /usr/share/keyrings/
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;~&lt;/span&gt;&lt;span&gt; ❯ sudo apt-get update
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;~&lt;/span&gt;&lt;span&gt; ❯ sudo apt-get&lt;/span&gt;&lt;span style="color: #d7dbe0;"&gt; -y&lt;/span&gt;&lt;span&gt; install cuda-toolkit-11-8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note the change in the last line - I wanted to pin the specific version. This installs a lot of stuff for you: &lt;code&gt;nvprof&lt;/code&gt;, &lt;code&gt;nvcc&lt;/code&gt;, &lt;code&gt;nvprune&lt;/code&gt;, &lt;code&gt;libcublas-dev&lt;/code&gt;, &lt;code&gt;libcufft&lt;/code&gt;, &lt;code&gt;libcusparse&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #011627; color: #d6deeb;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #addb67;"&gt;~&lt;/span&gt;&lt;span&gt; ❯ ls&lt;/span&gt;&lt;span style="color: #d7dbe0;"&gt; -l&lt;/span&gt;&lt;span&gt; /usr/local
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;total&lt;/span&gt;&lt;span&gt; 36
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;drwxr-xr-x&lt;/span&gt;&lt;span&gt;  2 root root 4096 Aug 24 11:48 bin
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;lrwxrwxrwx&lt;/span&gt;&lt;span&gt;  1 root root   22 Aug 24 11:48 cuda -&lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; /etc/alternatives/cuda
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;lrwxrwxrwx&lt;/span&gt;&lt;span&gt;  1 root root   25 Aug 24 11:48 cuda-11 -&lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; /etc/alternatives/cuda-11
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;drwxr-xr-x&lt;/span&gt;&lt;span&gt; 15 root root 4096 Aug 24 11:48 cuda-11.8
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;drwxr-xr-x&lt;/span&gt;&lt;span&gt;  2 root root 4096 Feb 10  2023 etc
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;drwxr-xr-x&lt;/span&gt;&lt;span&gt;  2 root root 4096 Feb 10  2023 games
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;drwxr-xr-x&lt;/span&gt;&lt;span&gt;  2 root root 4096 Feb 10  2023 include
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;drwxr-xr-x&lt;/span&gt;&lt;span&gt;  3 root root 4096 Feb 10  2023 lib
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;lrwxrwxrwx&lt;/span&gt;&lt;span&gt;  1 root root    9 Feb 10  2023 man -&lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; share/man
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;drwxr-xr-x&lt;/span&gt;&lt;span&gt;  2 root root 4096 Feb 10  2023 sbin
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;drwxr-xr-x&lt;/span&gt;&lt;span&gt;  6 root root 4096 Mar  7 17:10 share
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;drwxr-xr-x&lt;/span&gt;&lt;span&gt;  2 root root 4096 Feb 10  2023 src
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I'm not entirely sure why there's two separate &lt;code&gt;cuda&lt;/code&gt; and &lt;code&gt;cuda-11&lt;/code&gt; folders, and they don't seem to be symlinks to each other. This is a bit concerning, but I'm not going to worry about it too much. My &lt;code&gt;ldconfig -p | grep libcublas&lt;/code&gt; points to the &lt;code&gt;/usr/local/cuda&lt;/code&gt; variant, but when I check &lt;code&gt;nvcc&lt;/code&gt; there, it says the version of the toolkit is 11.8.&lt;/p&gt;
&lt;p&gt;Note, however, that it doesn't install &lt;code&gt;cuDNN&lt;/code&gt; or &lt;code&gt;TensorRT&lt;/code&gt; (aka &lt;code&gt;nvinfer&lt;/code&gt;). So let's do that next.&lt;/p&gt;
&lt;h4 id="cudnn-and-tensorrt"&gt;cuDNN and TensorRT&lt;/h4&gt;
&lt;p&gt;NVIDIA now has a deep learning installation guide here: &lt;a href="https://docs.nvidia.com/cudnn/index.html"&gt;NVIDIA cuDNN&lt;/a&gt;. I personally grabbed cuDNN v8.9.4 for CUDA 11.x, "Local Intaller for Ubuntu22.04 x86_64 (Deb)", and then the rest of the instructions from that linked page worked fine.&lt;/p&gt;
&lt;p&gt;Just in case it's unclear, the runtime library installation command was &lt;code&gt;sudo apt-get install libcudnn8=8.9.4.25-1+cuda11.8&lt;/code&gt; (after you've set up the local repository). I didn't install the code samples. After running &lt;code&gt;sudo ldconfig&lt;/code&gt;, I was able to find cuDNN using &lt;code&gt;ldconfig -p | grep libcudnn&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For TensorRT, the instructions are a bit confusing but you can basically do the same thing:&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #011627; color: #d6deeb;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #addb67;"&gt;~&lt;/span&gt;&lt;span&gt; ❯ sudo dpkg&lt;/span&gt;&lt;span style="color: #d7dbe0;"&gt; -i&lt;/span&gt;&lt;span&gt; nv-tensorrt-local-repo-ubuntu2204-8.6.1-cuda-11.8_1.0-1_amd64.deb
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;~&lt;/span&gt;&lt;span&gt; ❯ sudo cp /var/nv-tensorrt-local-repo-ubuntu2204-8.6.1-cuda-11.8/nv-tensorrt-local-0628887B-keyring.gpg /usr/share/keyrings/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The latter command is the output of the former, so you don't really need to worry about version information - just download the right &lt;code&gt;.deb&lt;/code&gt; from the link, and then copy and paste the keyring command.&lt;/p&gt;
&lt;p&gt;From here, I installed the full runtime, but &lt;em&gt;not&lt;/em&gt; the Python packages - I like to do that inside virtualenvs (since, for example, you might want a different base Python version).&lt;/p&gt;
&lt;h3 id="python-packages"&gt;Python packages&lt;/h3&gt;
&lt;p&gt;Now you can install torch like you always wanted (in a virtualenv, right?):&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #011627; color: #d6deeb;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #addb67;"&gt;pip3&lt;/span&gt;&lt;span&gt; install torch torchvision torchaudio&lt;/span&gt;&lt;span style="color: #d7dbe0;"&gt; --index-url&lt;/span&gt;&lt;span&gt; https://download.pytorch.org/whl/cu118
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure you check it works!&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #011627; color: #d6deeb;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #addb67;"&gt;~&lt;/span&gt;&lt;span&gt; ❯ python&lt;/span&gt;&lt;span style="color: #d7dbe0;"&gt; -c &lt;/span&gt;&lt;span style="color: #d9f5dd;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #ecc48d;"&gt;import torch; print(torch.cuda.is_available())&lt;/span&gt;&lt;span style="color: #d9f5dd;"&gt;&amp;quot;
&lt;/span&gt;&lt;span style="color: #addb67;"&gt;True
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="uninstall"&gt;Uninstall&lt;/h3&gt;
&lt;p&gt;I had to temporarily uninstall as part of messing everything up, and it wasn't too bad. Mostly I purged some apt packages using stuff like:&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #011627; color: #d6deeb;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #addb67;"&gt;sudo&lt;/span&gt;&lt;span&gt; apt-get&lt;/span&gt;&lt;span style="color: #d7dbe0;"&gt; --purge&lt;/span&gt;&lt;span&gt; remove &lt;/span&gt;&lt;span style="color: #d9f5dd;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #ecc48d;"&gt;cuda*&lt;/span&gt;&lt;span style="color: #d9f5dd;"&gt;&amp;quot;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and swapped &lt;code&gt;cuda&lt;/code&gt; for &lt;code&gt;nvidia&lt;/code&gt; and &lt;code&gt;nsight&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This gets rid of &lt;em&gt;most&lt;/em&gt; things, but as mentioned above, this will &lt;em&gt;keep&lt;/em&gt; &lt;code&gt;nvidia-smi&lt;/code&gt; around, since that's from the &lt;code&gt;C:\Windows\System32\lxss\lib\&lt;/code&gt; install, which is kept in &lt;code&gt;/usr/lib/wsl/lib/&lt;/code&gt;. To remove those, you need to uninstall the NVIDIA graphics driver from your Windows machine (note for me this caused Alacritty to break, but Powershell let me access WSL just fine).&lt;/p&gt;
&lt;footer class="footnotes"&gt;
&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-1"&gt;
&lt;p&gt;As far as can tell! I'm putting this out there and hoping Cunningham's law will get the right answer out, but that depends on you to correct me. Please email me at (fullname) [@] outlook.com. &lt;a href="https://rachitsingh.com/notes/wsl-cuda/#fr-1-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/footer&gt;</description><author>Rachit Singh</author><pubDate>Thu, 24 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://rachitsingh.com/notes/wsl-cuda/</guid></item><item><title>What Sound Does Your Microwave?</title><link>https://backendhance.com/blog/2023/what-sound-does-your-microwave/</link><description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;p&gt;Kennst du das? Du machst ein Gerät an, dass du immer wieder benutzt. Über Jahre. Du kennst das Geräusch, dass es macht.&lt;/p&gt;
&lt;p&gt;Das rythmische &amp;ldquo;&lt;em&gt;schwu schwu schwu&lt;/em&gt;&amp;rdquo;, wenn du die Spülmaschine anstellst.&lt;/p&gt;
&lt;p&gt;Oder das bekannte &amp;ldquo;&lt;em&gt;klick&lt;/em&gt;&amp;rdquo; wenn der Ofen angeht.&lt;/p&gt;
&lt;p&gt;Dein Signal: alles läuft. Weiter machen!&lt;/p&gt;
&lt;h2 id="doch-was-wenn-es-plötzlich-anders-klingt"&gt;
&lt;a class="anchor" href="#doch-was-wenn-es-pl%c3%b6tzlich-anders-klingt"&gt;Doch was, wenn es plötzlich anders klingt?&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Es nicht mehr deinen Erwartungen entspricht?&lt;/p&gt;
&lt;p&gt;Genau das ist mir mit meiner Mikrowelle passiert.&lt;/p&gt;
&lt;p&gt;Ich machte sie - wie gewohnt - an. Ich erwartete ein &amp;ldquo;&lt;em&gt;djeeehhh&lt;/em&gt;&amp;rdquo;.&lt;/p&gt;</description><author>Backendhance</author><pubDate>Thu, 24 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/what-sound-does-your-microwave/</guid></item><item><title>An Enhertu innovation map</title><link>https://atelfo.github.io/2023/08/24/enhertu-innovation-map.html</link><description/><author>Alex’s blog</author><pubDate>Thu, 24 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://atelfo.github.io/2023/08/24/enhertu-innovation-map.html</guid></item><item><title>A simple Stimulus Tabs Controller</title><link>https://railsnotes.xyz/blog/simple-stimulus-tabs-controller</link><description>In this article, I share a handy Stimulus controller for adding tabs to your Rails apps. I also show you a different variation of the controller, and how to add it to your views. Plus, I've included a handy tip to avoid the annoying "flashing in" effect that can happen if you're not careful.</description><author>RailsNotes, the Ruby on Rails guides you wished you had.</author><pubDate>Thu, 24 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://railsnotes.xyz/blog/simple-stimulus-tabs-controller</guid></item><item><title>Adblocking with a clear conscience</title><link>https://seirdy.one/notes/2023/08/22/adblocking-with-a-clear-conscience/</link><description>&lt;aside&gt;&lt;p&gt;
	Reply to &lt;span class="h-cite u-in-reply-to"&gt;&lt;cite class="p-name"&gt;&lt;a class="u-url" href="https://nzsocial.net/@tinfoilhat/110931692229194778"&gt;do you pay to remove ads, or continue to view content while breaking the agreement with ad blocking&lt;/a&gt;&lt;/cite&gt; by
		&lt;span class="h-card vcard p-author"&gt;&lt;a class="u-url url" href="https://nzsocial.net/@tinfoilhat"&gt;&lt;span class="p-name fn n"&gt;tinfoilhat&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;
	&lt;/span&gt;
&lt;/p&gt;&lt;/aside&gt;&lt;p&gt;&lt;a href="https://seirdy.one/notes/2022/08/12/user-agents-set-the-terms/"&gt;There is no such agreement on the web&lt;/a&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;On the users’s end, we don’t have advance notice that a link destination will contain malware (such as ads). The page has already downloaded; the content is already on our device before we agreed to anything. We were handed the goods and only told they had a price after leaving the store.&lt;/li&gt;&lt;li&gt;On a site owner’s end, Terms of Service should not a shield to enable discrimination. ToS that discriminate against marginalized groups have historically warranted civil disobedience and lawsuits ending in legal reform that outlawed such practices; why should ToS discriminating against neurodivergent users be any different?&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;I have ADHD and overstimulation sensitivity. Requiring me to view ads is discriminatory. So yes, I would violate the fuck out of such a ToS with a clearer conscience than the site owners, and side with the plaintiffs should the site ever face an accessibility lawsuit.&lt;/p&gt;</description><author>All content on Seirdy’s Home</author><pubDate>Thu, 24 Aug 2023 02:04:07 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/notes/2023/08/22/adblocking-with-a-clear-conscience/</guid></item><item><title>PET 2001 Emulator + 6502 Updates</title><link>https://www.masswerk.at/nowgobang/2023/pet-updates</link><description>Yet another software update notification.</description><author>mass:werk – Now Go Bang!</author><pubDate>Wed, 23 Aug 2023 09:30:00 GMT</pubDate><guid isPermaLink="true">https://www.masswerk.at/nowgobang/2023/pet-updates</guid></item><item><title>rsync with a different user</title><link>https://nicolaiarocci.com/rsync-with-a-different-user/</link><description>&lt;p&gt;Today I learned how to rsync with a user different than the one connected to the remote. Why would one want to do such a
thing? The data I need to download from that server is owned by &amp;lsquo;backup,&amp;rsquo; a different, service-only user. I wanted to
avoid going the change-permissions slippery route and allow my user direct access to the data.&lt;/p&gt;
&lt;p&gt;Looking at the rsync documentation, I learned about the nifty &lt;code&gt;--rsync-path=PROGRAM&lt;/code&gt; &lt;a href="https://download.samba.org/pub/rsync/rsync.1#opt--rsync-path"&gt;option&lt;/a&gt;:&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Wed, 23 Aug 2023 09:05:25 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/rsync-with-a-different-user/</guid></item><item><title>Like a Bosnian pressure cooker</title><link>https://kajic.com/post/726402294988537856</link><description>&lt;p&gt;Like a Bosnian pressure cooker&lt;/p&gt;&lt;p&gt;Whistling in the Kansas Sun &lt;/p&gt;&lt;p&gt;Now Missouri &lt;/p&gt;</description><author>ka-yich</author><pubDate>Wed, 23 Aug 2023 03:40:12 GMT</pubDate><guid isPermaLink="true">https://kajic.com/post/726402294988537856</guid></item><item><title>How to use Jupyter Notebooks to do Machine Learning and AI tasks</title><link>https://zackproser.com/blog/how-to-use-jupyter-notebooks</link><description>Jupyter Notebooks are surprisingly easy to get started with - especially when using GitHub and Google Colab</description><author>Zachary Proser</author><pubDate>Wed, 23 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/how-to-use-jupyter-notebooks</guid></item><item><title>Semantic Search with TypeScript and Pinecone</title><link>https://zackproser.com/videos/typescript-semantic-search</link><description>Join Roie Schwaber-Cohen and I as we first discuss and then review the code for doing semantic search with Pinecone's vector database</description><author>Zachary Proser</author><pubDate>Wed, 23 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/videos/typescript-semantic-search</guid></item><item><title>💡 Automated integration from Eero into Pihole</title><link>https://www.amishbhadeshia.co.uk/posts/automate-eero-into-pihole/</link><description>Automating the integration between Eero and PiHole to allow home network devices to show up as client in PiHole.</description><author>Amish's Blog</author><pubDate>Wed, 23 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.amishbhadeshia.co.uk/posts/automate-eero-into-pihole/</guid></item><item><title>Where did the Rust go?</title><link>https://softwaremaxims.com/blog/memory-safety-end-history</link><description>There is a term that is on a lot of lips lately. “Memory Safety”. The theme of the early 10s for software security is “Move to memory-safe languages”. You hear and see it everywhere</description><author>Musings about software</author><pubDate>Wed, 23 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://softwaremaxims.com/blog/memory-safety-end-history</guid></item><item><title>Containerized services on a home server</title><link>https://avikdas.com/2023/08/23/containerized-services-on-a-home-server.html</link><description>With my mini PC server set up with Debian, I prepared the server for actually running useful services. This time, I decided I would go all in with containers, hoping that will keep my applications self-contained enough that I don’t have to think about different applications stepping on each other.</description><author>Avik Das</author><pubDate>Wed, 23 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://avikdas.com/2023/08/23/containerized-services-on-a-home-server.html</guid></item><item><title>Quickly make any LÖVE app programmable from within the app</title><link>http://akkartik.name/post/love-repl</link><description>&lt;p&gt;
It's a very common workflow. Type out a &lt;a href="https://love2d.org"&gt;LÖVE&lt;/a&gt;
app. Try running it. Get an error, go back to the source code.

&lt;p&gt;
How can we do this &lt;em&gt;from within the LÖVE app&lt;/em&gt;? So there's nothing to
install?

&lt;p&gt;
This is a story about a hundred lines of code that do it. I'm probably not the
first to discover the trick, but I hadn't seen it before and it feels a bit
magical.

&lt;p&gt;
&lt;a href="https://forum.malleable.systems/t/adding-malleability-to-any-love-app/90"&gt;Read more&lt;/a&gt;</description><author>Kartik Agaram</author><pubDate>Tue, 22 Aug 2023 21:51:16 GMT</pubDate><guid isPermaLink="true">http://akkartik.name/post/love-repl</guid></item><item><title>AWAsary v1.2.2</title><link>https://tig.pt/awasary-v1-2-2/</link><description>&lt;figure class="kg-card kg-bookmark-card"&gt;&lt;a class="kg-bookmark-container" href="https://apps.apple.com/gh/app/awsary-aws-dictionary/id1634871091?ref=tig.pt"&gt;&lt;div class="kg-bookmark-content"&gt;&lt;div class="kg-bookmark-title"&gt;&amp;#x200e;AWSary - AWS Dictionary&lt;/div&gt;&lt;small&gt;&lt;div class="kg-bookmark-description"&gt;&amp;#x200e;AWSary is the ultimate resource for AWS Cloud Consultants, students, and anyone looking to stay up-to-date with AWS services. With this app, you&amp;#x2019;ll have access to a comprehensive AWS dictionary, allowing you to easily search and learn about various AWS services.&lt;/div&gt;&lt;/small&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;</description><author>Tiago Rodrigues</author><pubDate>Tue, 22 Aug 2023 21:36:00 GMT</pubDate><guid isPermaLink="true">https://tig.pt/awasary-v1-2-2/</guid></item><item><title>Wii Nunchuk as a USB HID controller</title><link>https://ciesie.com/project/wii_nunchuk_usb_controller/</link><description>&lt;h1 id="tldr"&gt;TL;DR&lt;a href="#tldr"&gt; &amp;lt;&lt;/a&gt;
  
&lt;/h1&gt;
&lt;p&gt;You can find the source code &lt;a href="https://gitlab.com/bluewii"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I used the Wii Nunchuk controller to prototype one handed controller for creating
computer graphics. I used the &lt;strong&gt;Blue Pill&lt;/strong&gt; board which carries a &lt;strong&gt;STM32F1&lt;/strong&gt;
microcontroller with a &lt;strong&gt;built-in USB peripheral&lt;/strong&gt;. That allowed me to implement
it as a true &lt;strong&gt;USB HID (Human Interface Device)&lt;/strong&gt; for the &lt;strong&gt;Windows OS&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The results were disappointing. My &lt;strong&gt;USB HID&lt;/strong&gt; implementation was poor. I have fixed
several important bugs in &lt;a href="http://ciesie.com/project/ble_graphics_controller/"&gt;another project&lt;/a&gt;.&lt;/p&gt;</description><author>ciesie.com</author><pubDate>Tue, 22 Aug 2023 19:05:58 GMT</pubDate><guid isPermaLink="true">https://ciesie.com/project/wii_nunchuk_usb_controller/</guid></item><item><title>Comments 2</title><link>https://mfashby.net/posts/2023-08-22-comments-2/</link><description/><author>Home on mfashby.net</author><pubDate>Tue, 22 Aug 2023 16:48:41 GMT</pubDate><guid isPermaLink="true">https://mfashby.net/posts/2023-08-22-comments-2/</guid></item><item><title>Why is a mood tracker from 2016 still the best?</title><link>https://www.nothingeasyaboutthis.com/moodapp/</link><description>In 2016, Boris Berenberg built MoodApp to track his mood and solve personal issues. Find out why it's still unbeaten in mood tracking functionality.</description><author>Nothing Easy About This</author><pubDate>Tue, 22 Aug 2023 14:30:22 GMT</pubDate><guid isPermaLink="true">https://www.nothingeasyaboutthis.com/moodapp/</guid></item><item><title>You Should Use: Excalidraw</title><link>http://evantravers.com/articles/2023/08/22/you-should-use-excalidraw/</link><description>&lt;p&gt;&lt;img alt="" src="https://images.evantravers.com/articles/2023/08/cycle.png" /&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="https://excalidraw.com/"&gt;Excalidraw&lt;/a&gt; is a free whiteboarding app I use constantly. Its expressive but simple tooling focuses on collaboration and sense-making instead of obsessing with perfection. It's simply the best.&lt;/p&gt;
&lt;h2&gt;&lt;a class="anchor" href="#simple-tools" id="simple-tools"&gt;&lt;/a&gt;Simple Tools&lt;/h2&gt;
&lt;p&gt;&lt;img alt="tools" src="https://images.evantravers.com/articles/2023/08/tools.png" /&gt;&lt;/p&gt;
&lt;p&gt;If you can hold a marker, you can use a whiteboard. It's a level playing field. Excalidraw is mouse-centric but usable on a touch screen. If you have a screen you can participate.&lt;/p&gt;
&lt;h2&gt;&lt;a class="anchor" href="#sharing" id="sharing"&gt;&lt;/a&gt;Sharing&lt;/h2&gt;
&lt;p&gt;It has the simplest security model. You can share a unique link, anyone with that link can edit anything. It's straightforward and a delight after doing the permissions dance with so many other tools.&lt;/p&gt;
&lt;h2&gt;&lt;a class="anchor" href="#keyboard-shortcuts" id="keyboard-shortcuts"&gt;&lt;/a&gt;Keyboard Shortcuts&lt;/h2&gt;
&lt;p&gt;As you may know, I &lt;a href="https://evantravers.com/articles/tags/keyboards/"&gt;love my keyboard shortcuts&lt;/a&gt; and it has really smart shortcuts covering most of it't shortcuts covering most of it's tooling. If nothing else, &lt;code&gt;⌘⌥-C&lt;/code&gt; to copy your selection to clipboard as PNG is perfect for sharing a quick thought in a chat or pasting into a Confluence wiki.&lt;/p&gt;
&lt;h2&gt;&lt;a class="anchor" href="#open-source" id="open-source"&gt;&lt;/a&gt;Open Source&lt;/h2&gt;
&lt;p&gt;&lt;img alt="obsidian" src="https://images.evantravers.com/articles/2023/08/tolerance.png" /&gt;&lt;/p&gt;
&lt;p&gt;Open-source means that I can use a plugin to embed Excalidraw in my Obsidian note-taking app as editable vector illustrations for my notes… I can even modify them on my phone or iPad.&lt;/p&gt;
&lt;h2&gt;&lt;a class="anchor" href="#" id=""&gt;&lt;/a&gt;…&lt;/h2&gt;
&lt;p&gt;Give it a try next time you find yourself waving your hands in the air on a video-call.&lt;/p&gt;</description><author>trv.rs</author><pubDate>Tue, 22 Aug 2023 14:17:00 GMT</pubDate><guid isPermaLink="true">http://evantravers.com/articles/2023/08/22/you-should-use-excalidraw/</guid></item><item><title>The Death of the Search Engine</title><link>https://lagomor.ph/2023/08/the-death-of-the-search-engine/</link><description>&lt;p&gt;In the early days of Google and Yahoo the best hack for getting your website to the top of rankings was &lt;a href="https://en.wikipedia.org/wiki/Keyword_stuffing" rel="noopener noreferrer" target="_blank"&gt;Keyword Stuffing&lt;/a&gt;. You&amp;rsquo;d make the font blend in with the background and jam in as many relevant (or in many cases completely irrelevant, depending on your intent) keywords as you possibly could. Your users couldn&amp;rsquo;t see them, but search engines could, and they&amp;rsquo;d push you up the rankings, since with so many keywords the website in question must be super relevant to what you&amp;rsquo;re looking for.&lt;/p&gt;</description><author>Home on Lagomorph</author><pubDate>Tue, 22 Aug 2023 09:00:00 GMT</pubDate><guid isPermaLink="true">https://lagomor.ph/2023/08/the-death-of-the-search-engine/</guid></item><item><title>Permissions Policy header bloat</title><link>https://seirdy.one/notes/2023/08/03/permissions-policy-header-bloat/</link><description>&lt;aside&gt;&lt;p&gt;
	Reply to &lt;span class="h-cite u-in-reply-to"&gt;&lt;cite class="p-name"&gt;&lt;a class="u-url" href="https://github.com/w3c/webappsec-permissions-policy/issues/481#issuecomment-1259139696"&gt;I think we should be comparing wins relative to HTTP/3 with the latest in header compression&lt;/a&gt;&lt;/cite&gt; by
		&lt;span class="h-card vcard p-author"&gt;&lt;a class="u-url url" href="https://annevankesteren.nl/"&gt;&lt;span class="p-name fn n"&gt;Anne van Kesteren&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;
	&lt;/span&gt;
&lt;/p&gt;&lt;/aside&gt;&lt;p&gt;I’m a browser “with the latest in header compression”, fetching a web page. I race a TCP-based ALPN run against an HTTPS record lookup (Chromium’s behavior). Either the HTTP/2 ALPN wins the race, or the HTTPS DNS record does not exist. Both are, and will remain, common scenarios. So I fetch the page over HTTP/2. This is the initial request; dynamic HPACK hasn’t kicked in. I download a 1.56kb HTTP response header:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Permissions-Policy: accelerometer=(),ambient-light-sensor=(),attribution-reporting=(),autoplay=(),battery=(),bluetooth=(),browsing-topics=(),camera=(),ch-device-memory=(),ch-downlink=(),ch-dpr=(),ch-ect=(),ch-lang=(),ch-partitioned-cookies=(),ch-prefers-color-scheme=(),ch-prefers-reduced-motion=(),ch-rtt=(),ch-save-data=(),ch-ua=(),ch-ua-arch=(),ch-ua-bitness=(),ch-ua-form-factor=(),ch-ua-full=(),ch-ua-full-version=(),ch-ua-full-version-list=(),ch-ua-mobile=(),ch-ua-model=(),ch-ua-platform=(),ch-ua-platform=(),ch-ua-platform-version=(),ch-ua-reduced=(),ch-ua-wow64=(),ch-viewport-height=(),ch-viewport-width=(),ch-width=(),clipboard-read=(),clipboard-write=(),compute-pressure=(),conversion-measurement=(),cross-origin-isolated=(),direct-sockets=(),display-capture=(),document-domain=(),encrypted-media=(),execution-while-not-rendered=(),execution-while-out-of-viewport=(),federated-credentials=(),focus-without-user-activation=(),fullscreen=(),gamepad=(),geolocation=(),gyroscope=(),hid=(),identity-credentials-get=(),idle-detection=(),interest-cohort=(),join-ad-interest-group=(),keyboard-map=(),local-fonts=(),magnetometer=(),microphone=(),midi=(),navigation-override=(),otp-credentials=(),payment=(),picture-in-picture=(),private-state-token-issuance=(),private-state-token-redemption=(),publickey-credentials-get=(),run-ad-auction=(),screen-wake-lock=(),serial=(),shared-autofill=(),shared-storage=(),smart-card=(),speaker-selection=(),storage-access=(),storage-access-api=(),sync-script=(),sync-xhr=(),trust-token-redemption=(),unload=(),usb=(),vertical-scroll=(),wake-lock=(),web-share=(),window-placement=(),xr-spatial-tracking=()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, through &lt;code&gt;Alt-Svc&lt;/code&gt; or an HTTPS record lookup, I discover HTTP/3 support. I download a render-blocking asset over an upgraded HTTP/3 connection. This is the first HTTP/3 request; dynamic QPACK compression hasn’t kicked in. I download a 1.56kb HTTP response header:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Permissions-Policy: accelerometer=(),ambient-light-sensor=(),attribution-reporting=(),autoplay=(),battery=(),bluetooth=(),browsing-topics=(),camera=(),ch-device-memory=(),ch-downlink=(),ch-dpr=(),ch-ect=(),ch-lang=(),ch-partitioned-cookies=(),ch-prefers-color-scheme=(),ch-prefers-reduced-motion=(),ch-rtt=(),ch-save-data=(),ch-ua=(),ch-ua-arch=(),ch-ua-bitness=(),ch-ua-form-factor=(),ch-ua-full=(),ch-ua-full-version=(),ch-ua-full-version-list=(),ch-ua-mobile=(),ch-ua-model=(),ch-ua-platform=(),ch-ua-platform=(),ch-ua-platform-version=(),ch-ua-reduced=(),ch-ua-wow64=(),ch-viewport-height=(),ch-viewport-width=(),ch-width=(),clipboard-read=(),clipboard-write=(),compute-pressure=(),conversion-measurement=(),cross-origin-isolated=(),direct-sockets=(),display-capture=(),document-domain=(),encrypted-media=(),execution-while-not-rendered=(),execution-while-out-of-viewport=(),federated-credentials=(),focus-without-user-activation=(),fullscreen=(),gamepad=(),geolocation=(),gyroscope=(),hid=(),identity-credentials-get=(),idle-detection=(),interest-cohort=(),join-ad-interest-group=(),keyboard-map=(),local-fonts=(),magnetometer=(),microphone=(),midi=(),navigation-override=(),otp-credentials=(),payment=(),picture-in-picture=(),private-state-token-issuance=(),private-state-token-redemption=(),publickey-credentials-get=(),run-ad-auction=(),screen-wake-lock=(),serial=(),shared-autofill=(),shared-storage=(),smart-card=(),speaker-selection=(),storage-access=(),storage-access-api=(),sync-script=(),sync-xhr=(),trust-token-redemption=(),unload=(),usb=(),vertical-scroll=(),wake-lock=(),web-share=(),window-placement=(),xr-spatial-tracking=()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I’ve burned 3.2kb on what may be a small page, for one header. Rendering hasn’t started yet. Add headers this is meant to complement (&lt;code&gt;Document-Policy&lt;/code&gt;, CSP, etc.), all with an expanding list of directives, and header size becomes a problem. This is before we consider clients that lack HPACK/QPACK support (nearly all HTTP libraries I’ve used), or resources on other domains.&lt;/p&gt;&lt;p&gt;CSP had a good solution: grouping the fetch directives that existed at the time under &lt;code&gt;default-src&lt;/code&gt;. Similarly, we can try defining a large set of permissions to group under one directive. Every few years, we could add a new meta-directive to keep forward- and backward-compatibility while also keeping header size from growing out of control.&lt;/p&gt;</description><author>All content on Seirdy’s Home</author><pubDate>Tue, 22 Aug 2023 07:57:42 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/notes/2023/08/03/permissions-policy-header-bloat/</guid></item><item><title>Slowing down Pandora's Box</title><link>https://www.georgeyw.com/slowing-down-pandoras-box/</link><description>Editorial for the Canadian Science Policy Centre</description><author>This Is My True Name</author><pubDate>Tue, 22 Aug 2023 07:53:17 GMT</pubDate><guid isPermaLink="true">https://www.georgeyw.com/slowing-down-pandoras-box/</guid></item><item><title>August 22, 2023</title><link>https://brntn.me/notes/v_ux5V5Auq4/</link><description>&lt;div class="h-review"&gt;
                &lt;p&gt;
                    🎬 Watched
                    &lt;span class="h-item"&gt;
                        &lt;a class="p-name u-url u-watch-of" href="https://www.imdb.com/title/tt17007120/"&gt;The Beanie Bubble (2023)&lt;/a&gt;.
            ★★☆&lt;/span&gt;.
                &lt;/p&gt;&lt;p&gt;&lt;img alt="Poster for The Beanie Bubble (2023)" src="https://media.brntn.me/postie/400dc0e2.jpg" /&gt;&lt;/p&gt;&lt;/div&gt;</description><author>Brenton Cleeland</author><pubDate>Tue, 22 Aug 2023 04:45:38 GMT</pubDate><guid isPermaLink="true">https://brntn.me/notes/v_ux5V5Auq4/</guid></item><item><title>ssh_find</title><link>https://porkrind.org/missives/ssh_find/</link><description>I can&amp;#8217;t remember who wrote this first, but my friend and I have been using some version of this shell snippet for years: ssh_find() { for agent in "$SSH_AUTH_SOCK" /tmp/ssh-*/agent.* /tmp/com.apple.launchd.*/Listeners* /tmp/keyring-*/ssh; do SSH_AUTH_SOCK="$agent" ssh-add -l 2&amp;#62;/dev/null case "$?" in # 0 some keys, 1 no keys, 2 no agent 0) export SSH_AUTH_SOCK="$agent"; echo "Found &amp;#8230; &lt;a class="more-link" href="https://porkrind.org/missives/ssh_find/"&gt;Continue reading &lt;span class="screen-reader-text"&gt;ssh_find&lt;/span&gt;&lt;/a&gt;</description><author>Missives</author><pubDate>Tue, 22 Aug 2023 04:30:34 GMT</pubDate><guid isPermaLink="true">https://porkrind.org/missives/ssh_find/</guid></item><item><title>Opengraph dynamic social images</title><link>https://zackproser.com/blog/opengraph-integration</link><description>I built a custom opengraph image with '@vercel/og' that includes a fallback image for index pages</description><author>Zachary Proser</author><pubDate>Tue, 22 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/opengraph-integration</guid></item><item><title>My custom Tesco Clubcard pkpass</title><link>https://xenodium.com/my-custom-tesco-clubcard-pkpass</link><description>&lt;p&gt;My significant other and I had two plastic Tesco Clubcards. I lost mine, so I took a picture of hers. I was fairly certain a barcode photo would scan just as well at self-checkout, and it did.&lt;/p&gt;
&lt;p&gt;This got me thinking about Apple's Wallet &lt;a href="https://en.wikipedia.org/wiki/PKPASS"&gt;pkpasses&lt;/a&gt;. I don't really know much about them. Could I potentially create my own &lt;code&gt;.pkpass&lt;/code&gt;? If I could just include the same barcode as in the photo, it should do the job just fine.&lt;/p&gt;
&lt;p&gt;Now I should mention, &lt;a href="https://apps.apple.com/gb/app/tesco-grocery-clubcard/id389581236"&gt;Tesco does have an app on the App Store&lt;/a&gt;. If you just want the official Wallet pass on your iPhone, use that. But I was curious about whether or not I could create my own pass.&lt;/p&gt;
&lt;p&gt;Turns out I &lt;em&gt;can&lt;/em&gt;. I followed Apple's &lt;a href="https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/PassKit_PG/YourFirst.html"&gt;building your first pass&lt;/a&gt; which runs you through &lt;a href="https://developer.apple.com/help/account/configure-app-capabilities/create-wallet-identifiers-and-certificates"&gt;creating Wallet identifiers/certificates&lt;/a&gt;, editing &lt;code&gt;pass.json&lt;/code&gt;, and downloading/building &lt;a href="https://developer.apple.com/services-account/download?path=/iOS/Wallet_Support_Materials/WalletCompanionFiles.zip"&gt;signpass&lt;/a&gt; (the utility used to sign &lt;code&gt;.pass&lt;/code&gt; bundles).&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;signpass&lt;/code&gt; utility is included in WalletCompanionFiles.zip, which comes with a handful of sample passes.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;WalletCompanionFiles
│
├── SamplePasses
│   │
│   ├── BoardingPass.pass
│   ├── Coupon.pass
│   ├── Event.pass
│   ├── Event.pkpass
│   ├── Generic.pass
│   └── StoreCard.pass
│       │
│       ├── pass.json
│       └── ...
└── signpass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Being a rewards card, I opted to look into &lt;code&gt;StoreCard.pass&lt;/code&gt;, but like all other passes, the &lt;code&gt;barcode&lt;/code&gt; itself is what makes each pass scannable. The barcode details are specified in the bundles's &lt;code&gt;pass.json&lt;/code&gt; file. I needed to figure out the relevant values describing the Tesco barcode.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-json"&gt;&amp;quot;barcode&amp;quot;: {
  &amp;quot;format&amp;quot;: &amp;quot;???&amp;quot;,
  &amp;quot;message&amp;quot;: &amp;quot;???&amp;quot;,
  &amp;quot;messageEncoding&amp;quot;: &amp;quot;???&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I had no clue what values I should use for a Tesco Clubcard. I did, however, have a photo of the barcode I needed. This is in fact what prompted looking into &lt;a href="https://xenodium.com/emacs-scan-this-qrcode"&gt;scanning barcodes from Emacs&lt;/a&gt;, which worked just great. It gave me all the crucial bits for the Clubcard.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-json"&gt;&amp;quot;barcode&amp;quot;: {
  &amp;quot;format&amp;quot;: &amp;quot;PKBarcodeFormatCode128&amp;quot;,
  &amp;quot;message&amp;quot;: &amp;quot;1234567890123456&amp;quot;,  // not my actual Clubcard number of course.
  &amp;quot;messageEncoding&amp;quot;: &amp;quot;iso-8859-1&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's all that's needed for the barcode section, the most useful part of the pass. We're not done though. We also need our registered Wallet identifiers, so the &lt;code&gt;signpass&lt;/code&gt; utility can sign.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-json"&gt;&amp;quot;passTypeIdentifier&amp;quot;: &amp;quot;my.com.identifier.passmaker&amp;quot;, // also not my actual one.
&amp;quot;teamIdentifier&amp;quot;: &amp;quot;AAABBBCCCD&amp;quot;, // nor this one.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We should be able to sign the pass with the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.bash"&gt;signpass -p StoreCard.pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We're technically done. We now have a working card, but it looks just like the sample store card included in WalletCompanionFiles.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/my-custom-tesco-clubcard-pkpass/lemons.png" /&gt;&lt;/p&gt;
&lt;p&gt;What's the fun in that? Now that I can make my own Clubcard, let's customize it!&lt;/p&gt;
&lt;p&gt;For imagery, I replaced a couple of images in the .pass bundle:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c"&gt;StoreCard.pass
│
├── pass.json
├── icon.png
├── logo.png // replaced
└── strip.png // replaced
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I replaced &lt;code&gt;logo.png&lt;/code&gt; using a &lt;a href="https://en.wikipedia.org/wiki/File:Tesco_Logo.svg"&gt;Tesco logo I found on Wikipedia&lt;/a&gt;. I had initially removed &lt;code&gt;strip.png&lt;/code&gt;, but that made the card feel a little empty. I was thinking of using a Tesco carrier bag to bulk the space up. While I didn't find a suitable bag image, I did land on &amp;quot;&lt;a href="https://banksyexplained.com/very-little-helps-2008/"&gt;Very Little Helps, 2008&lt;/a&gt;&amp;quot;. Using my limited &lt;a href="https://www.gimp.org/"&gt;GIMP&lt;/a&gt; skills, I cropped one of the images and also replaced &lt;code&gt;strip.png&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The remaining customizations took place in &lt;code&gt;pass.json&lt;/code&gt; and should be fairly self-explanatory. There's the text shown in all labels as well as three customizable colours (background, label, and foreground).&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-json"&gt;{
  &amp;quot;formatVersion&amp;quot;: 1,
  &amp;quot;passTypeIdentifier&amp;quot;: &amp;quot;my.com.identifier.passmaker&amp;quot;, // also not my actual one.
  &amp;quot;teamIdentifier&amp;quot;: &amp;quot;AAABBBCCCD&amp;quot;, // nor this one.
  &amp;quot;serialNumber&amp;quot;: &amp;quot;AnySerialNumberYouWant&amp;quot;,
  &amp;quot;barcode&amp;quot;: {
    &amp;quot;format&amp;quot;: &amp;quot;PKBarcodeFormatCode128&amp;quot;,
    &amp;quot;message&amp;quot;: &amp;quot;1234567890123456&amp;quot;,
    &amp;quot;messageEncoding&amp;quot;: &amp;quot;iso-8859-1&amp;quot;
  },
  &amp;quot;organizationName&amp;quot;: &amp;quot;Not Tesco of course&amp;quot;,
  &amp;quot;description&amp;quot;: &amp;quot;Not a Tesco reqards card&amp;quot;,
  &amp;quot;logoText&amp;quot;: &amp;quot;Clubcard&amp;quot;,
  &amp;quot;foregroundColor&amp;quot;: &amp;quot;rgb(255, 255, 255)&amp;quot;,
  &amp;quot;labelColor&amp;quot;: &amp;quot;rgb(255, 255, 255)&amp;quot;,
  &amp;quot;backgroundColor&amp;quot;: &amp;quot;rgb(2, 81, 158)&amp;quot;, // Blue for that Tesco look
  &amp;quot;storeCard&amp;quot;: {
    &amp;quot;auxiliaryFields&amp;quot;: [
      {
        &amp;quot;key&amp;quot;: &amp;quot;membership&amp;quot;,
        &amp;quot;label&amp;quot;: &amp;quot;Member since 2023&amp;quot;,
        &amp;quot;value&amp;quot;: &amp;quot;&amp;quot;
      },
      {
        &amp;quot;key&amp;quot;: &amp;quot;membership2&amp;quot;,
        &amp;quot;label&amp;quot;: &amp;quot;Expires sometime&amp;quot;,
        &amp;quot;value&amp;quot;: &amp;quot;&amp;quot;
      }
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;…and with all that, here's what my very own custom Tesco Clubcard pkpass looks like. As you can appreciate, my image-editing skills aren't all that great, but hey this will do for now.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/my-custom-tesco-clubcard-pkpass/bsy.png" /&gt;&lt;/p&gt;
&lt;h2&gt;Update&lt;/h2&gt;
&lt;p&gt;Redditor u/stupergenius &lt;a href="https://www.reddit.com/r/programming/comments/15y4c65/comment/jxa1obg/?utm_source=share&amp;amp;utm_medium=web2x&amp;amp;context=3"&gt;suggested using the image's original background color&lt;/a&gt;. Nice suggestion. Tweaked via pass.json:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-json"&gt;&amp;quot;foregroundColor&amp;quot;: &amp;quot;rgb(2, 81, 158)&amp;quot;,
&amp;quot;labelColor&amp;quot;: &amp;quot;rgb(15, 58, 105)&amp;quot;,
&amp;quot;backgroundColor&amp;quot;: &amp;quot;rgb(166, 202, 214)&amp;quot;,
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/my-custom-tesco-clubcard-pkpass/bsy-light.png" /&gt;&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Tue, 22 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/my-custom-tesco-clubcard-pkpass</guid></item><item><title>Sleeping at Night</title><link>https://www.marginalia.nu/log/86-sleep/</link><description>&lt;p&gt;I&amp;rsquo;ve started going on a long walk each morning immediately as I wake up, and it&amp;rsquo;s had the unexpected side-effect of fixing my broken circadian rhythm.&lt;/p&gt;
&lt;p&gt;For decades, as long as I can remember, I&amp;rsquo;ve been what you might consider a serious night owl. Regardless of how long I slept or when I woke up, I would get nothing requiring any sort of thought done until sometime after lunch, and it wasn&amp;rsquo;t really until late at night that my brain really kicked into gear.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Tue, 22 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/86-sleep/</guid></item><item><title>I am 83% normal</title><link>https://vit.baisa.cz/notes/personal/i-am-83-percent-normal/</link><description>my results from an online assessment tool</description><author>Vít Baisa</author><pubDate>Tue, 22 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://vit.baisa.cz/notes/personal/i-am-83-percent-normal/</guid></item><item><title>56% of McNugget Carbon is Corn</title><link>https://taylor.town/carbon-nuggets</link><description>Corn isotopes everywhere! Has science gone too far?</description><author>taylor.town</author><pubDate>Tue, 22 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/carbon-nuggets</guid></item><item><title>Intro To Effect, Part 5: Software Transactional Memory in Effect</title><link>https://ybogomolov.me/05-effect-stm</link><description>&lt;p&gt;Software Transactional Memory (STM) offers a powerful and elegant solution to managing concurrency in functional programming environments. Through STM, you can create composable and maintainable concurrent applications with ease, effectively dealing with shared mutable state. In this article, we’ll dive into the concepts of STM, its core principles, and how it can be effectively utilized to build concurrent systems with data consistency and atomic operations.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;Intro to Effect series:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href="https://ybogomolov.me/01-effect-intro"&gt;What is Effect?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://ybogomolov.me/02-effect-handling-errors"&gt;Handling Errors&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://ybogomolov.me/03-effect-managing-dependencies"&gt;Managing Dependencies&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://ybogomolov.me/04-effect-concurrency"&gt;Concurrency in Effect&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Software Transactional Memory in Effect&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Software Transactional Memory is an advanced concurrency control technique that simplifies concurrent programming by treating shared memory updates as transactions, allowing multiple operations to be composed in an atomic and consistent manner.&lt;/p&gt;

&lt;p&gt;An in-depth understanding of STM requires diving into various aspects, such as its design principles, the core data structures, and the operations that work within the STM context. Let’s explore each of these features to gain a solid understanding of STM and see it in action.&lt;/p&gt;

&lt;h1 id="what-is-software-transactional-memory"&gt;What is Software Transactional Memory&lt;/h1&gt;

&lt;h2 id="design-principles"&gt;Design Principles&lt;/h2&gt;

&lt;p&gt;STM in Effect is built upon the following core principles:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Atomicity&lt;/em&gt;: All operations inside an STM transaction are atomic, meaning either all updates are committed, or none are committed.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Consistency&lt;/em&gt;: Transactions maintain the consistency of the shared state, ensuring any updates preserve the invariants of your data structures.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Isolation&lt;/em&gt;: Transactions are isolated from each other, and the updates of one transaction are invisible to others until they are committed.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Composability&lt;/em&gt;: STMs are effects that can be easily combined and composed together, retaining their functional error-handling capabilities.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="core-data-structures"&gt;Core Data Structures&lt;/h2&gt;

&lt;p&gt;STM is built around two main data structures: &lt;code class="language-plaintext highlighter-rouge"&gt;TRef&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;STM&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id="tref"&gt;TRef&lt;/h3&gt;

&lt;p&gt;The primary data structure is &lt;code class="language-plaintext highlighter-rouge"&gt;TRef&amp;lt;A&amp;gt;&lt;/code&gt;, a mutable &lt;strong&gt;t&lt;/strong&gt;ransactional &lt;strong&gt;ref&lt;/strong&gt;erence to a shared memory location that allows transactional operations. TRefs encapsulate the state and provide the necessary operations to manage state updates transactionally.&lt;/p&gt;

&lt;p&gt;A &lt;code class="language-plaintext highlighter-rouge"&gt;TRef&amp;lt;A&amp;gt;&lt;/code&gt; provides a way to transactionally modify a shared state as part of a transactional effect. The primary operations of a &lt;code class="language-plaintext highlighter-rouge"&gt;TRef&lt;/code&gt; are &lt;code class="language-plaintext highlighter-rouge"&gt;get&lt;/code&gt;, &lt;code class="language-plaintext highlighter-rouge"&gt;set&lt;/code&gt;, and &lt;code class="language-plaintext highlighter-rouge"&gt;update&lt;/code&gt;. When you inspect the API of TRef, you’ll see that all these operations and their combinations return the &lt;code class="language-plaintext highlighter-rouge"&gt;STM&lt;/code&gt; type, which could be thought of as another effectful type akin to &lt;code class="language-plaintext highlighter-rouge"&gt;Effect&amp;lt;R, E, A&amp;gt;&lt;/code&gt;:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="c1"&gt;// module: TRef.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;STM&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;STM&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;STM&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It is important to note that while a &lt;code class="language-plaintext highlighter-rouge"&gt;TRef&amp;lt;A&amp;gt;&lt;/code&gt; serves as a transactional &lt;strong&gt;mutable&lt;/strong&gt; reference, the value it contains should be &lt;strong&gt;immutable&lt;/strong&gt;.&lt;/p&gt;

&lt;h3 id="stm"&gt;STM&lt;/h3&gt;

&lt;p&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;STM&amp;lt;R, E, A&amp;gt;&lt;/code&gt; is a data type that models a transactional effect and defines the context within which STM operations and combinators are executed. It is essentially an effect type that can fail with an error (of type &lt;code class="language-plaintext highlighter-rouge"&gt;E&lt;/code&gt;) or succeed with a value (of type &lt;code class="language-plaintext highlighter-rouge"&gt;A&lt;/code&gt;) while requiring a context of type &lt;code class="language-plaintext highlighter-rouge"&gt;R&lt;/code&gt; to execute.&lt;/p&gt;

&lt;p&gt;STM models an atomic operation that works with TRefs and provides the building blocks for creating consistent and isolated transactions in a concurrent setting. Its transactions consist of read, write, and update operations on TRefs, as well as combinators like &lt;code class="language-plaintext highlighter-rouge"&gt;check&lt;/code&gt; to add conditions, and &lt;code class="language-plaintext highlighter-rouge"&gt;orElse&lt;/code&gt; to compose multiple STM effects. These transactions are atomic, consistent, and isolated from each other.&lt;/p&gt;

&lt;p&gt;To run an STM transaction, we use the &lt;code class="language-plaintext highlighter-rouge"&gt;commit&lt;/code&gt; operation, which turns an STM effect into a regular effect (&lt;code class="language-plaintext highlighter-rouge"&gt;STM&amp;lt;R, E, A&amp;gt;&lt;/code&gt; → &lt;code class="language-plaintext highlighter-rouge"&gt;Effect&amp;lt;R, E, A&amp;gt;&lt;/code&gt;). Once converted, the committed transaction can be executed in the broader effect context, benefiting from all the error handling and resource management capabilities.&lt;/p&gt;

&lt;p&gt;Let’s take a look at a short example of using STM to model a counter that can be safely accessed from any fiber. To begin, we need to add the &lt;a href="https://github.com/Effect-TS/stm"&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;@effect/stm&lt;/code&gt;&lt;/a&gt; package to the project:&lt;/p&gt;

&lt;div class="language-sh highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;npm i @effect/stm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;First, we will use the STM effect to implement the &lt;code class="language-plaintext highlighter-rouge"&gt;increment&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;decrement&lt;/code&gt; operations. They will be working off a simple &lt;code class="language-plaintext highlighter-rouge"&gt;TRef&amp;lt;number&amp;gt;&lt;/code&gt; that represents our shared concurrent counter:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;STM&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@effect/stm/STM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;TRef&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@effect/stm/TRef&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;increment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;STM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STM&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;TRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decrement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;STM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STM&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;TRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now we can write a program that increments the counter 10 times in one fiber, and decrements 10 times in another one:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Fiber&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;effect&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;program&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;incrementFiber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forkAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;STM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;)))));&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decrementFiber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forkAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;STM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;decrement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;)))));&lt;/span&gt;

  &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;joinAll&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;incrementFiber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;decrementFiber&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;finalCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;STM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Final count: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;finalCount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// will be 0, because all increments are matched with a decrement&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runPromise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;program&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We launch 10 increment fibers and 10 decrement fibers concurrently. Each increment and decrement operation updates the shared &lt;code class="language-plaintext highlighter-rouge"&gt;Counter&lt;/code&gt; state atomically, demonstrating STM’s ability to handle concurrent state changes without race conditions.&lt;/p&gt;

&lt;h3 id="journal"&gt;Journal&lt;/h3&gt;

&lt;p&gt;There is a third data structure, &lt;code class="language-plaintext highlighter-rouge"&gt;Journal&lt;/code&gt;, that plays a crucial role in STM runtime. It is an internal data structure used to record all the changes made to TRefs within a transaction. It is crucial for implementing atomicity, as the journal records changes that are eventually committed or discarded, depending on the transaction’s success or failure.&lt;/p&gt;

&lt;p&gt;A journal is a key internal data structure in STM, crucial for maintaining atomicity, consistency, and isolation in transactions. Essentially, it is an in-memory log of a transaction’s read, write, and conditional check operations on TRefs (transactional references). The journal isolates a transaction’s updates, only applying modifications to the shared memory (TRefs) upon successful commitment. Under the hood, &lt;code class="language-plaintext highlighter-rouge"&gt;Journal&lt;/code&gt; is just a &lt;code class="language-plaintext highlighter-rouge"&gt;Map&amp;lt;TRef&amp;lt;unknown&amp;gt;, Entry&amp;gt;&lt;/code&gt;, where &lt;code class="language-plaintext highlighter-rouge"&gt;Entry&lt;/code&gt; is a single versioned entry in the journal that represents the most recent state of the TRef.&lt;/p&gt;

&lt;p&gt;During a transaction, the STM runtime consults the journal for read or write operations on TRefs. If a TRef is accessed for the first time, the runtime creates a new journal entry with the TRef’s initial state, ensuring a consistent snapshot of all involved TRefs. Changes to TRefs are recorded in the journal without altering the shared memory location.&lt;/p&gt;

&lt;p&gt;Upon commitment, the runtime verifies if journal conditions still hold. If valid, updates are applied atomically to the shared memory. Otherwise, the transaction is suspended, and the runtime retries when any involved references change.&lt;/p&gt;

&lt;h1 id="stm-in-action"&gt;STM In Action&lt;/h1&gt;

&lt;p&gt;Counters are a simple use case, and perhaps do not demonstrate the full capabilities of STM. To better illustrate the power of STM, let’s explore a more realistic example, such as reserving seats for flights.&lt;/p&gt;

&lt;p&gt;Our main data model will hold a single field of &lt;code class="language-plaintext highlighter-rouge"&gt;TRef&amp;lt;number&amp;gt;&lt;/code&gt; type that represents the current number of available seats on the flight:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Flight&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;seats&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next, let’s imagine that a passenger wants to book a flight. This operation &lt;em&gt;decreases&lt;/em&gt; the number of available seats, but it cannot &lt;em&gt;decrease it below zero&lt;/em&gt;. In STM, there is a function called &lt;code class="language-plaintext highlighter-rouge"&gt;check&lt;/code&gt; that ensures that a transaction continues only if a predicate holds true. The downside of this function is that it retries &lt;em&gt;silently&lt;/em&gt;, so we cannot cap the number of retries or somehow react to the failure. So in this example, I will explicitly return &lt;code class="language-plaintext highlighter-rouge"&gt;STM.fail&lt;/code&gt;:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;STM&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@effect/stm/STM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;TRef&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@effect/stm/TRef&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Schedule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;effect&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;OverbookedError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;_tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OverbookedError&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OverbookedError&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bookSeats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Flight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;seatsToReserve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;OverbookedError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;STM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;seats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;seats&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seats&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;seatsToReserve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;STM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;OverbookedError&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;seats&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;seats&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;seatsToReserve&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;STM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt; millis`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Schedule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addDelay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Schedule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recurs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;millis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What happens in &lt;code class="language-plaintext highlighter-rouge"&gt;.pipe&lt;/code&gt; needs an additional explanation. First, we turn &lt;code class="language-plaintext highlighter-rouge"&gt;STM&lt;/code&gt; into &lt;code class="language-plaintext highlighter-rouge"&gt;Effect&lt;/code&gt; by committing our transaction with &lt;code class="language-plaintext highlighter-rouge"&gt;STM.commit&lt;/code&gt;. Next, we delay the whole effect by some random amount of milliseconds. This simulates the decision-making process by the passenger, or database/network delay — whatever motivates the delay better for you, my reader. Finally, we make the whole effect retry, and for this case, we construct a custom &lt;em&gt;schedule&lt;/em&gt; — a strategy of retrying. In this case, we retry a maximum of 5 times with a 50 milliseconds delay between attempts.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;N.B. See how &lt;code class="language-plaintext highlighter-rouge"&gt;OverbookedError&lt;/code&gt; was defined? Having the &lt;code class="language-plaintext highlighter-rouge"&gt;_tag&lt;/code&gt; field present in this class enables us to partially catch that error using &lt;code class="language-plaintext highlighter-rouge"&gt;Effect.catchTag&lt;/code&gt;! This is one of the recommended ways of modelling errors when using Effect.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If we return to our problem domain, we can imagine that sometimes a passenger wants to &lt;em&gt;return&lt;/em&gt; their ticket. It is a straightforward function — we just increment the number of available seats by the returned amount:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unbookSeats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Flight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;seatsToUnbook&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;STM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;seats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;seats&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;seats&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;seats&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;seatsToUnbook&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;STM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// or even simpler, using a single `update`:&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unbookSeats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Flight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;seatsToUnbook&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;STM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;seats&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seats&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;seats&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;seatsToUnbook&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now we can start writing our main program.&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;program&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;seats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Canadair Regional Jet 200 with 50 seats&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Flight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;seats&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As usual, we construct a new &lt;code class="language-plaintext highlighter-rouge"&gt;TRef&lt;/code&gt; instance. Make a note that it is an effectful operation, so you need to await its completion with &lt;code class="language-plaintext highlighter-rouge"&gt;yield* _(..)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next, let’s spawn 30 fibers, each representing a passenger that wants to book N seats for their family (&lt;code class="language-plaintext highlighter-rouge"&gt;N ∈ [1, 5]&lt;/code&gt;). On a rare occasion, a passenger might return &lt;code class="language-plaintext highlighter-rouge"&gt;M ≤ N&lt;/code&gt; seats:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;  &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;seatsToReserve&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextIntBetween&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bookingResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;either&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bookSeats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;seatsToReserve&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

          &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;Either&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bookingResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;onLeft&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
                &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Failed to reserve &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;seatsToReserve&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; seats for passenger #&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; after 5 attempts`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

              &lt;span class="na"&gt;onRight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
                &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Reserved &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;seatsToReserve&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; seats for passenger #&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

                  &lt;span class="c1"&gt;// Sometimes a passenger will return their booked seats:&lt;/span&gt;
                  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="c1"&gt;// We simulate passenger's hesitance here:&lt;/span&gt;
                    &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;succeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt; millis`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

                    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;seatsReturned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextIntBetween&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;seatsToReserve&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seatsReturned&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                      &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;unbookSeats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;seatsReturned&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
                      &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Unbooked &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;seatsReturned&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; seats for passenger #&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                  &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}),&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
          &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;withLogSpan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Reserve#&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;concurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unbounded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally, we want to see how many seats remain after all bookings:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;remainingSeats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;STM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;seats&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

  &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Remaining seats: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;remainingSeats&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When we run this program using &lt;code class="language-plaintext highlighter-rouge"&gt;Effect.runPromise(program)&lt;/code&gt;, we might see something similar to this:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-log"&gt;timestamp=2023-08-22T12:48:30.335Z level=INFO fiber=#19 message="Reserved 1 seats for passenger #19" Reserve#19=2ms
timestamp=2023-08-22T12:48:30.337Z level=INFO fiber=#21 message="Reserved 1 seats for passenger #21" Reserve#21=4ms
timestamp=2023-08-22T12:48:30.339Z level=INFO fiber=#7 message="Reserved 4 seats for passenger #07" Reserve#07=8ms
timestamp=2023-08-22T12:48:30.342Z level=INFO fiber=#16 message="Reserved 3 seats for passenger #16" Reserve#16=10ms
timestamp=2023-08-22T12:48:30.344Z level=INFO fiber=#2 message="Reserved 4 seats for passenger #02" Reserve#02=14ms
timestamp=2023-08-22T12:48:30.346Z level=INFO fiber=#20 message="Reserved 3 seats for passenger #20" Reserve#20=13ms
timestamp=2023-08-22T12:48:30.348Z level=INFO fiber=#6 message="Reserved 3 seats for passenger #06" Reserve#06=17ms
timestamp=2023-08-22T12:48:30.349Z level=INFO fiber=#22 message="Reserved 2 seats for passenger #22" Reserve#22=16ms
timestamp=2023-08-22T12:48:30.349Z level=INFO fiber=#1 message="Reserved 3 seats for passenger #01" Reserve#01=20ms
timestamp=2023-08-22T12:48:30.351Z level=INFO fiber=#27 message="Reserved 1 seats for passenger #27" Reserve#27=18ms
timestamp=2023-08-22T12:48:30.355Z level=INFO fiber=#10 message="Reserved 3 seats for passenger #10" Reserve#10=23ms
timestamp=2023-08-22T12:48:30.356Z level=INFO fiber=#13 message="Reserved 4 seats for passenger #13" Reserve#13=24ms
timestamp=2023-08-22T12:48:30.360Z level=INFO fiber=#17 message="Reserved 1 seats for passenger #17" Reserve#17=28ms
timestamp=2023-08-22T12:48:30.362Z level=INFO fiber=#25 message="Reserved 2 seats for passenger #25" Reserve#25=29ms
timestamp=2023-08-22T12:48:30.363Z level=INFO fiber=#14 message="Reserved 1 seats for passenger #14" Reserve#14=31ms
timestamp=2023-08-22T12:48:30.367Z level=INFO fiber=#5 message="Reserved 3 seats for passenger #05" Reserve#05=36ms
timestamp=2023-08-22T12:48:30.369Z level=INFO fiber=#18 message="Reserved 2 seats for passenger #18" Reserve#18=36ms
timestamp=2023-08-22T12:48:30.369Z level=INFO fiber=#26 message="Reserved 3 seats for passenger #26" Reserve#26=36ms
timestamp=2023-08-22T12:48:30.372Z level=INFO fiber=#28 message="Reserved 2 seats for passenger #28" Reserve#28=39ms
timestamp=2023-08-22T12:48:30.372Z level=INFO fiber=#29 message="Reserved 1 seats for passenger #29" Reserve#29=39ms
timestamp=2023-08-22T12:48:30.372Z level=INFO fiber=#22 message="Unbooked 1 seats for passenger #22" Reserve#22=39ms
timestamp=2023-08-22T12:48:30.377Z level=INFO fiber=#16 message="Unbooked 2 seats for passenger #16" Reserve#16=45ms
timestamp=2023-08-22T12:48:30.379Z level=INFO fiber=#15 message="Reserved 3 seats for passenger #15" Reserve#15=47ms
timestamp=2023-08-22T12:48:30.383Z level=INFO fiber=#7 message="Unbooked 3 seats for passenger #07" Reserve#07=52ms
timestamp=2023-08-22T12:48:30.387Z level=INFO fiber=#23 message="Reserved 2 seats for passenger #23" Reserve#23=54ms
timestamp=2023-08-22T12:48:30.387Z level=INFO fiber=#24 message="Reserved 1 seats for passenger #24" Reserve#24=54ms
timestamp=2023-08-22T12:48:30.392Z level=INFO fiber=#30 message="Reserved 1 seats for passenger #30" Reserve#30=59ms
timestamp=2023-08-22T12:48:30.396Z level=INFO fiber=#18 message="Unbooked 1 seats for passenger #18" Reserve#18=63ms
timestamp=2023-08-22T12:48:30.403Z level=INFO fiber=#5 message="Unbooked 2 seats for passenger #05" Reserve#05=72ms
timestamp=2023-08-22T12:48:30.417Z level=INFO fiber=#9 message="Reserved 3 seats for passenger #09" Reserve#09=86ms
timestamp=2023-08-22T12:48:30.417Z level=INFO fiber=#23 message="Unbooked 1 seats for passenger #23" Reserve#23=84ms
timestamp=2023-08-22T12:48:30.418Z level=INFO fiber=#4 message="Reserved 1 seats for passenger #04" Reserve#04=88ms
timestamp=2023-08-22T12:48:30.426Z level=INFO fiber=#11 message="Reserved 1 seats for passenger #11" Reserve#11=94ms
timestamp=2023-08-22T12:48:30.436Z level=INFO fiber=#15 message="Unbooked 2 seats for passenger #15" Reserve#15=104ms
timestamp=2023-08-22T12:48:30.448Z level=INFO fiber=#26 message="Unbooked 1 seats for passenger #26" Reserve#26=115ms
timestamp=2023-08-22T12:48:30.453Z level=INFO fiber=#9 message="Unbooked 1 seats for passenger #09" Reserve#09=122ms
timestamp=2023-08-22T12:48:30.465Z level=INFO fiber=#28 message="Unbooked 1 seats for passenger #28" Reserve#28=132ms
timestamp=2023-08-22T12:48:30.482Z level=INFO fiber=#3 message="Reserved 4 seats for passenger #03" Reserve#03=152ms
timestamp=2023-08-22T12:48:31.166Z level=ERROR fiber=#12 message="Failed to reserve 3 seats for passenger #12 after 5 attempts" Reserve#12=834ms
timestamp=2023-08-22T12:48:31.176Z level=ERROR fiber=#8 message="Failed to reserve 3 seats for passenger #08 after 5 attempts" Reserve#08=845ms
timestamp=2023-08-22T12:48:31.179Z level=INFO fiber=#0 message="Remaining seats: 2"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here we see how different passengers book various amounts of seats, sometimes returning them back. And for the last two poor fellas — the system could not satisfy their demands, so after 5 attempts their bookings were rejected.&lt;/p&gt;

&lt;h1 id="conclusion"&gt;Conclusion&lt;/h1&gt;

&lt;p&gt;As we saw, Software Transactional Memory offers an effective and sophisticated approach to managing concurrency in functional programming. By adhering to core principles such as atomicity, consistency, isolation, and composability, STM simplifies the coordination of shared mutable states, ensuring a more robust and reliable concurrent programming experience. Leveraging transactional references (TRefs) and STM effects, developers can construct concurrent programs that are both composable and maintainable, avoiding common issues and race conditions that often arise in concurrent software development. Overall, STM allows for the creation of dependable, scalable, and efficient concurrent applications that align well with the functional programming paradigm.&lt;/p&gt;</description><author/><pubDate>Tue, 22 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ybogomolov.me/05-effect-stm</guid></item><item><title>❌ RuntimeError: no .dist-info at ... has pip for pipenv install managed via asdf-vm1</title><link>https://jdsalaro.com/howto/fix-pipenv-asdf-runtimeerror-no-dist-info-has-pip/</link><description>&lt;span style="font-size: 1.5em;"&gt;ℹ️&lt;/span&gt; Several folks who are not using asdf-vm but are facing similar issues have asked me for advice, I’ve detailed how you can troubleshoot and fix this issue on /howto/fix-pipenv-runtimeerror-no-dist-info-has-pip.Having said that, I’ve mentioned in several occasions that pipenv is my virtual environment manager of choice in Python projects and that I use it in tandem with asdf-vm1, which allows me to seemlesly switch between Python versions depending on the project I’m working on. However, after updating one of my Ubuntu test systems (20.04.6 LTS) and installing a couple of Python versions via asdf-vm, pipenv suddenly stopped working. In this brief HOWTO I hope to provide a couple of hints as to how you may debug and ultimately fix a PipEnv installation which finds itself in a weird state.</description><author>Jayson Salazar Rodriguez | @jdsalaro | Blog</author><pubDate>Tue, 22 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jdsalaro.com/howto/fix-pipenv-asdf-runtimeerror-no-dist-info-has-pip/</guid></item><item><title>Automator Is Pretty Sweet</title><link>https://porkrind.org/missives/automator-is-pretty-sweet/</link><description>I played around with Apple&amp;#8217;s Automator app when it was first released, but I could never really find a good use for it (I wrote some actions but I would never reliably use them for whatever reason). But I did eventual write one that I use constantly. It&amp;#8217;s a &amp;#8220;Quick Action&amp;#8221;, which means it shows &amp;#8230; &lt;a class="more-link" href="https://porkrind.org/missives/automator-is-pretty-sweet/"&gt;Continue reading &lt;span class="screen-reader-text"&gt;Automator Is Pretty Sweet&lt;/span&gt;&lt;/a&gt;</description><author>Missives</author><pubDate>Tue, 22 Aug 2023 02:14:48 GMT</pubDate><guid isPermaLink="true">https://porkrind.org/missives/automator-is-pretty-sweet/</guid></item><item><title>It’s not magic (part 1)</title><link>https://letterstoanewdeveloper.com/2023/08/21/its-not-magic-part-1/</link><description>Dear new developer, I remember the first time I pulled up MapQuest in the 2000s. I looked for directions to a place I&amp;#8217;d never been. A custom map popped up, which was cool. But even more impressive was the turn by turn directions. I printed them both out and followed the instructions. That felt like &amp;#8230; &lt;a class="more-link" href="https://letterstoanewdeveloper.com/2023/08/21/its-not-magic-part-1/"&gt;Continue reading &lt;span class="screen-reader-text"&gt;It&amp;#8217;s not magic (part&amp;#160;1)&lt;/span&gt; &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;</description><author>Letters To A New Developer</author><pubDate>Mon, 21 Aug 2023 15:05:12 GMT</pubDate><guid isPermaLink="true">https://letterstoanewdeveloper.com/2023/08/21/its-not-magic-part-1/</guid></item><item><title>How to do a successful Hacker News launch</title><link>https://lucasfcosta.com/blog/hn-launch/</link><description>When it comes to writing, the #1 question I usually get asked is how do I get so many HN front pages with this blog.

This week, another one of my projects got to the front page for the first time, a...</description><author>Lucas F. Costa - Blog</author><pubDate>Mon, 21 Aug 2023 15:00:00 GMT</pubDate><guid isPermaLink="true">https://lucasfcosta.com/blog/hn-launch/</guid></item><item><title>Aliens (1986) Audio Description</title><link>https://brett.coulstock.id.au/aliens-1986-audio-description.html</link><description>Writing independent audio-description for the 1986 sci-fi/action/horror film Aliens.</description><author>Brett Coulstock - Writer</author><pubDate>Mon, 21 Aug 2023 12:00:00 GMT</pubDate><guid isPermaLink="true">https://brett.coulstock.id.au/aliens-1986-audio-description.html</guid></item><item><title>Respectfully Requesting RSS Feeds with Python</title><link>https://brntn.me/blog/respectfully-requesting-rss-feeds/</link><description>&lt;!-- # Respectfully Requesting RSS Feeds --&gt;

&lt;p&gt;I found myself writing code to fetch RSS (and &lt;a href="https://www.jsonfeed.org/"&gt;JSON&lt;/a&gt;) feeds, and was immediately reminded of &lt;a href="https://rachelbythebay.com/"&gt;Rachel By The Bay&lt;/a&gt;'s posts about feed reader behaviour [&lt;a href="https://rachelbythebay.com/w/2022/03/07/get/"&gt;1&lt;/a&gt;, &lt;a href="https://rachelbythebay.com/w/2023/01/18/http/"&gt;2&lt;/a&gt;, &lt;a href="https://rachelbythebay.com/w/2023/06/03/feed/"&gt;3&lt;/a&gt;].
Those posts go into detail about their expectations for feed readers.
This post will show you how to satisfy those requirements and a few others that I'm going to add.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://rachelbythebay.com/w/2023/01/18/http/"&gt;Feeds, updates, 200s, 304s, and now 429s&lt;/a&gt; can be summarised as requiring these steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Poll feeds at a reasonable interval&lt;/li&gt;
&lt;li&gt;Make conditional requests with If-Modified-Since or If-None-Match&lt;/li&gt;
&lt;li&gt;Handle 304 responses correctly&lt;/li&gt;
&lt;li&gt;Back off if you receive a 429&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I'm adding two other requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use a User-Agent that lets server admins know who you are&lt;/li&gt;
&lt;li&gt;Allow responses to be compressed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let's start with a simple Django model that represents our &lt;code&gt;Feed&lt;/code&gt;.
We'll slowly update the &lt;code&gt;fetch()&lt;/code&gt; function to retrieve the feed using &lt;a href="https://github.com/sesh/thttp"&gt;&lt;code&gt;thttp&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;class Feed(models.Model):
    url = models.URLField()

    def fetch(self):
        pass
&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I'm using Django and Python, but don't let that put you off!
The example should be trivial enough to convert to most other languages.&lt;/p&gt;
&lt;h3&gt;Poll feeds at a reasonable interval&lt;/h3&gt;
&lt;p&gt;Rachel talks about feed readers requesting feeds every two minutes.
This is clearly unreasonable for a blog that posts a few times a month at most.
But what is a reasonable frequency to request a feed of posts?&lt;/p&gt;
&lt;p&gt;You need to think about your use case and decide how important it is that you get the latest post immediately, as well as how frequently the feed will change.
Most blogs, and even most news sites, aren't adding new content every few minutes.&lt;/p&gt;
&lt;p&gt;If you are presenting the posts in a feed reader then hourly (or even slower) likely makes sense.
For use cases that trigger events on a new post (i.e. a push notification) you might request the feed more frequently (or use a push mechanism like &lt;a href="http://home.rsscloud.co/"&gt;rssCloud&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;The best option is to implement a &amp;quot;delay&amp;quot; that's based on the type of feed that your are requesting.
Use a sensible default (say 30-60 minutes), then either allow the user to change that delay or use the posting frequency to decide on the delay.&lt;/p&gt;
&lt;p&gt;Our model needs to keep two things in order to make this happen: the last time the feed was requested, and the minimum interval between checks.
In the &lt;code&gt;fetch()&lt;/code&gt; function we will check if the check_interval has passed and return early if it hasn't.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;class Feed(models.Model):
    url = models.URLField(help_text=&amp;quot;URL for the RSS/Atom feed&amp;quot;)

    last_checked_utc = models.DateTimeField(blank=True, null=True)
    check_interval = models.IntegerField(default=3600)

    def fetch(self):
        # ensure the check_interval has passed
        if (
            self.last_checked_utc
            and (datetime.now(tz=zoneinfo.ZoneInfo(&amp;quot;UTC&amp;quot;)) - self.last_checked_utc).seconds &amp;lt; self.check_interval
        ):
            logger.info(f&amp;quot;[fetch] {self.url} was checked less than {self.check_interval} seconds ago&amp;quot;)
            return
&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Because Django's DateTimeField has timezone support we are explicitly using UTC timestamps for our comparison here.&lt;/p&gt;
&lt;h3&gt;Make conditional requests with If-Modified-Since or If-None-Match&lt;/h3&gt;
&lt;p&gt;The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since"&gt;&lt;code&gt;If-Modified-Since&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match"&gt;&lt;code&gt;If-None-Match&lt;/code&gt;&lt;/a&gt; headers are part of the HTTP spec and give servers the option of returning &lt;code&gt;304 Not Modified&lt;/code&gt; instead of the regular response when handling GET requests.
They use slightly different mechanisms to achieve this.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;If-Modified-Since&lt;/code&gt; tells the server to only return content if it has been modified after the date provided.
In our example we will set this date to the last time that we requested the feed.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;If-None-Match&lt;/code&gt; relies on an &lt;code&gt;ETag&lt;/code&gt; (or entity tag) returned by the server.
When sending a response the server generates a unique &lt;code&gt;ETag&lt;/code&gt; value for that specific version of the response.
Our client keeps that value and sends it in the &lt;code&gt;If-None-Match&lt;/code&gt; header for subsequent requests.
If the generated tag would be the same then the server returns an empty response.&lt;/p&gt;
&lt;p&gt;In both cases a status code of &lt;code&gt;304&lt;/code&gt; is returned to indicate that there are no updates.&lt;/p&gt;
&lt;p&gt;We need to keep track of the &lt;code&gt;ETag&lt;/code&gt; value if it's returned, and have two headers that we need to (optionally) send on the request.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;class Feed(models.Model):
    url = models.URLField(help_text=&amp;quot;URL for the RSS/Atom feed&amp;quot;)

    last_checked_utc = models.DateTimeField(blank=True, null=True)
    check_interval = models.IntegerField(default=3600)

    last_etag = models.CharField(max_length=500, blank=True, default=&amp;quot;&amp;quot;)

    def fetch(self):
        # ensure the check_interval has passed
        # ...

        # send conditional request headers
        headers = {}
        if self.last_etag:
            headers[&amp;quot;If-None-Match&amp;quot;] = self.last_etag

        if self.last_checked_utc:
            headers[&amp;quot;If-Modified-Since&amp;quot;] = self.last_checked_utc.strftime(&amp;quot;%a, %d %m %Y %H:%M:%S GMT&amp;quot;)

        response = thttp.request(self.url, headers=headers)

        # save ETag and last checked timestamp
        if response.headers.get(&amp;quot;etag&amp;quot;):
            self.last_etag = response.headers[&amp;quot;etag&amp;quot;]

        self.last_checked_utc = datetime.now(tz=zoneinfo.ZoneInfo(&amp;quot;UTC&amp;quot;))
        self.save()
&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tthe &lt;code&gt;If-None-Match&lt;/code&gt; header will take precedence over the &lt;code&gt;If-Modified-Since&lt;/code&gt; value if both are provided, but there's really no harm in keeping the code simple and providing both.&lt;/p&gt;
&lt;h3&gt;Handle 304 responses correctly&lt;/h3&gt;
&lt;p&gt;When making conditional requests the &lt;code&gt;304 Not Modified&lt;/code&gt; response indicates that there is no new content.
The response body is likely empty in this case and you should stop processing that feed.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;class Feed(models.Model):
    url = models.URLField(help_text=&amp;quot;URL for the RSS/Atom feed&amp;quot;)

    last_checked_utc = models.DateTimeField(blank=True, null=True)
    check_interval = models.IntegerField(default=3600)

    last_etag = models.CharField(max_length=500, blank=True, default=&amp;quot;&amp;quot;)

    def fetch(self):
        # ensure the check_interval has passed
        # ...

        # send conditional request headers
        # ...

        # return immediately if 304 is returned
        if response.status == 304:
            # content is unchanged
            logger.info(f&amp;quot;[fetch] {self.url} is unchanged&amp;quot;)
            self.last_checked_utc = datetime.now(tz=zoneinfo.ZoneInfo(&amp;quot;UTC&amp;quot;))
            self.save()
            return

        # save ETag and last checked timestamp
        # ...
&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Because the &lt;code&gt;last_checked_utc&lt;/code&gt; value is used to determine whether we should make future requests we make sure to update it here.&lt;/p&gt;
&lt;h3&gt;Back off if you receive a 429&lt;/h3&gt;
&lt;p&gt;A &lt;code&gt;429 Too Many Request&lt;/code&gt; response is used to tell you that you're making request too quickly.
There are &lt;a href="https://en.wikipedia.org/wiki/Exponential_backoff"&gt;many strategies for managing backoffs&lt;/a&gt; (also called &lt;a href="https://www.tomwalton.blog/posts/summary-of-retry-strategies/"&gt;retry strategies&lt;/a&gt;) for HTTP requests, but here ours will be pretty simple:&lt;/p&gt;
&lt;p&gt;Let's double the check interval if we receive a response from the server that isn't a 200 or 304, up to a maximum of 24 hours.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;class Feed(models.Model):
    url = models.URLField(help_text=&amp;quot;URL for the RSS/Atom feed&amp;quot;)

    last_checked_utc = models.DateTimeField(blank=True, null=True)
    check_interval = models.IntegerField(default=3600)

    last_etag = models.CharField(max_length=500, blank=True, default=&amp;quot;&amp;quot;)

    def fetch(self):
        # ensure the check_interval has passed
        # ...

        # send conditional request headers
        # ...

        # return immediately if 304 is returned
        # ...

        # backoff if we receive a 429 response
        if response.status != 200:
            logger.info(f&amp;quot;[fetch] {self.url} returned {response.status}&amp;quot;)
            self.check_interval = self.check_interval + self.check_interval
            if self.check_interval &amp;gt; 60 * 60 * 24:
                self.check_interval = 60 * 60 * 24
            self.last_checked_utc = datetime.now(tz=zoneinfo.ZoneInfo(&amp;quot;UTC&amp;quot;))
            self.save()
            return

        # save ETag and last checked timestamp
        # ...
&lt;/pre&gt;&lt;/div&gt;&lt;h3&gt;Use a User-Agent that lets server admins know who you are&lt;/h3&gt;
&lt;p&gt;Our two bonus requirements are both fairly straight forward and are implemented as simple request headers.&lt;/p&gt;
&lt;p&gt;While it might be tempting to set the &lt;code&gt;User-Agent&lt;/code&gt; header to mimic &lt;a href="https://requestheaders.dev"&gt;a set of browser header&lt;/a&gt; you should really give the servers your are making requests to some information about your client.
Feeds are intended to be read by bots and servers should not block non-browser user agents.&lt;/p&gt;
&lt;p&gt;Some example feed reader agents are:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;Feedly/1.0 (+http://www.feedly.com/fetcher.html; XXXX subscribers;)
Feedbin feed-id:XXXXXXX - XXXX subscribers
Slackbot 1.0 (+https://api.slack.com/robots)
Overcast/1.0 Podcast Sync (XXXX subscribers; feed-id=XXXXXXX; +http://overcast.fm/)
CommaFeed/3.9.0 (https://github.com/Athou/commafeed)
&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Although not true of all of the above we will include both the name of our bot and a URL that can be used to find more details.
Convention dictates that URLs are preceded by &amp;quot;+&amp;quot; in your &lt;code&gt;User-Agent&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;class Feed(models.Model):
    url = models.URLField(help_text=&amp;quot;URL for the RSS/Atom feed&amp;quot;)

    last_checked_utc = models.DateTimeField(blank=True, null=True)
    check_interval = models.IntegerField(default=3600)

    last_etag = models.CharField(max_length=500, blank=True, default=&amp;quot;&amp;quot;)

    def fetch(self):
        # ensure the check_interval has passed
        # ...

        # send our user agent for all requests
        headers = {
            &amp;quot;User-Agent&amp;quot;: &amp;quot;ExampleBot (+https://brntn.me/blog/respectfully-requesting-rss-feeds/)&amp;quot;
        }

        # send conditional request headers
        # ...

        # return immediately if 304 is returned
        # ...

        # backoff if we receive a 429 response
        # ...

        # save ETag and last checked timestamp
        # ...
&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Don't forget to update that URL if you are copying this code into your project!&lt;/p&gt;
&lt;h3&gt;Allow responses to be compressed&lt;/h3&gt;
&lt;p&gt;If the library we are using to make HTTP requests supports it, we should set the appropriate &lt;code&gt;Accept-Encoding&lt;/code&gt; headers to let the server compress the response.
RSS feeds that contain many posts and full post bodies can get quite large.
The best case scenario for a server is to return a cached version of the compressed feed.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/sesh/thttp"&gt;&lt;code&gt;thttp&lt;/code&gt;&lt;/a&gt; has a deliberately limited feature set but it does support &lt;code&gt;gzip&lt;/code&gt; compression.
We can update our headers dictionary to allow this before making the request.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;class Feed(models.Model):
    url = models.URLField(help_text=&amp;quot;URL for the RSS/Atom feed&amp;quot;)

    last_checked_utc = models.DateTimeField(blank=True, null=True)
    check_interval = models.IntegerField(default=3600)

    last_etag = models.CharField(max_length=500, blank=True, default=&amp;quot;&amp;quot;)

    def fetch(self):
        # ensure the check_interval has passed
        # ...

        # send our user agent for all requests
        headers = {
            &amp;quot;User-Agent&amp;quot;: &amp;quot;ExampleBot (+https://brntn.me/blog/respectfully-requesting-rss-feeds/)&amp;quot;,
            &amp;quot;Accept-Encoding&amp;quot;: &amp;quot;gzip&amp;quot;,
        }

        # send conditional request headers
        # ...

        # return immediately if 304 is returned
        # ...

        # backoff if we receive a 429 response
        # ...

        # save ETag and last checked timestamp
        # ...
&lt;/pre&gt;&lt;/div&gt;&lt;h3&gt;Code Complete&lt;/h3&gt;
&lt;p&gt;That's it!
I hope this deep dive into feed fetching was helpful!&lt;/p&gt;
&lt;p&gt;The code below is a complete Django model that fetches an RSS feed in the most respectful way possible.&lt;/p&gt;
&lt;p&gt;The next step (for you!) is to actually process the feed and save those entries.
In Python I would recommend &lt;a href="https://github.com/kurtmckee/feedparser"&gt;feedparser&lt;/a&gt; for that job.
Most languages will have libraries for parsing the RSS feed's XML body so that you don't have to.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;import logging
import thttp
import zoneinfo

from datetime import datetime
from django.db import models

logger = logging.getLogger(&amp;quot;django&amp;quot;)


class Feed(models.Model):
    url = models.URLField(help_text=&amp;quot;URL for the RSS/Atom feed&amp;quot;)

    last_checked_utc = models.DateTimeField(blank=True, null=True)
    check_interval = models.IntegerField(default=3600)

    last_etag = models.CharField(max_length=500, blank=True, default=&amp;quot;&amp;quot;)

    def fetch(self):
        # ensure the check_interval has passed
        if (
            self.last_checked_utc
            and (
                datetime.now(tz=zoneinfo.ZoneInfo(&amp;quot;UTC&amp;quot;)) - self.last_checked_utc
            ).seconds
            &amp;lt; self.check_interval
        ):
            logger.info(
                f&amp;quot;[fetch] {self.url} was checked less than {self.check_interval} seconds ago&amp;quot;
            )
            return

        # send our user agent for all requests
        headers = {
            &amp;quot;User-Agent&amp;quot;: &amp;quot;ExampleBot (+https://brntn.me/blog/respectfully-requesting-rss-feeds/)&amp;quot;,
            &amp;quot;Accept-Encoding&amp;quot;: &amp;quot;gzip&amp;quot;,
        }

        # send conditional request headers
        headers = {}
        if self.last_etag:
            headers[&amp;quot;If-None-Match&amp;quot;] = self.last_etag

        if self.last_checked_utc:
            headers[&amp;quot;If-Modified-Since&amp;quot;] = self.last_checked_utc.strftime(
                &amp;quot;%a, %d %m %Y %H:%M:%S GMT&amp;quot;
            )

        response = thttp.request(self.url, headers=headers)

        # return immediately if 304 is returned
        if response.status == 304:
            # content is unchanged
            logger.info(f&amp;quot;[fetch] {self.url} is unchanged&amp;quot;)
            self.last_checked_utc = datetime.now(tz=zoneinfo.ZoneInfo(&amp;quot;UTC&amp;quot;))
            self.save()
            return

        # backoff if we receive a 429 response
        if response.status != 200:
            logger.info(f&amp;quot;[fetch] {self.url} returned {response.status}&amp;quot;)
            self.check_interval = self.check_interval + self.check_interval
            if self.check_interval &amp;gt; 60 * 60 * 24:
                self.check_interval = 60 * 60 * 24
            self.last_checked_utc = datetime.now(tz=zoneinfo.ZoneInfo(&amp;quot;UTC&amp;quot;))
            self.save()
            return

        # save ETag and last checked timestamp
        if response.headers.get(&amp;quot;etag&amp;quot;):
            self.last_etag = response.headers[&amp;quot;etag&amp;quot;]

        self.last_checked_utc = datetime.now(tz=zoneinfo.ZoneInfo(&amp;quot;UTC&amp;quot;))
        self.save()

        # TODO: Process the feed items and save them in our database

&lt;/pre&gt;&lt;/div&gt;</description><author>Brenton Cleeland</author><pubDate>Mon, 21 Aug 2023 05:45:00 GMT</pubDate><guid isPermaLink="true">https://brntn.me/blog/respectfully-requesting-rss-feeds/</guid></item><item><title>The Fifth Ideal: Customer Focus</title><link>https://backendhance.com/blog/2023/the-fifth-ideal-customer-focus/</link><description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;Das VPN sollte nicht von euch [Sysadmins] betrieben werden&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Nach diesem Satz sah ich in erschreckte Gesichter.&lt;/p&gt;
&lt;p&gt;Er ist mir herausgerutscht. Während dem wöchentlichen Jour Fixe zwischen den Entwicklern und Admins.&lt;/p&gt;
&lt;p&gt;Er kam auf die Frage was unsere Prioritäten sind.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Das traf ein Nerv.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Wahrscheinlich nicht, weil die Beteiligten emotional so stark am VPN hängen.&lt;/p&gt;
&lt;p&gt;Wahrscheinlich auch nicht, weil die Aussage falsch ist.&lt;/p&gt;
&lt;p&gt;Ne - es liegt eher daran, dass sie für das Team so unkonventionell ist.&lt;/p&gt;</description><author>Backendhance</author><pubDate>Mon, 21 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/the-fifth-ideal-customer-focus/</guid></item><item><title>The phrase "good enough" isn't fit for purpose</title><link>https://ntietz.com/blog/good-enough-vs-fit-for-purpose/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;Words matter.
First impressions matter.&lt;/p&gt;
&lt;p&gt;I'm reading &lt;a href="https://pragprog.com/titles/tpp20/the-pragmatic-programmer-20th-anniversary-edition/"&gt;The Pragmatic Programmer&lt;/a&gt; in a book club, and there's a section titled "Good-Enough Software".
In it, the authors expand that "the phrase 'good enough' does not imply sloppy or poorly produced code" and that it must still meet all requirements.
The rest of the section is a reasonable message that we should include users in the requirements process and not build things they don't need, since that has actual cost (both in money and schedule delays).&lt;/p&gt;
&lt;p&gt;I agree with the overall message.
We've all had the coworker who doesn't know when to stop polishing, doesn't know when to stop.
But I think the section is done a disservice by the phrase they chose to lead with.&lt;/p&gt;
&lt;p&gt;The phrase "good enough" carries with it a negative connotation.
It implies that you're cutting corners.
"Oh, it's good enough" isn't something you want your surgeon to say, it's not something you want to hear from your lawyer or your accountant.
It's not a prhase for professionals.&lt;/p&gt;
&lt;p&gt;Instead of things that are good enough, I'd rather we make things that are &lt;strong&gt;fit for purpose&lt;/strong&gt;.
The phrase "fit for purpose" doesn't carry the connotation of cutting corners, but of actively considering what is needed and ensuring that that's present.
Whatever you're describing has what it needs to do the job.&lt;/p&gt;
&lt;p&gt;These can often be used interchangeably.
My car is good enough to get me to my parents' house.
My car is fit for purpose for that drive.
The former makes you suspect that there's some reason we might think it's not?
While the latter gives confidence that it definitely is.&lt;/p&gt;
&lt;p&gt;Even though they mean the same thing, what they &lt;em&gt;communicate&lt;/em&gt; is far different.&lt;/p&gt;
&lt;p&gt;So, yeah.
It's easier to argue for, since you're not going against people's pride in their work by arguing for cutting corners.
And it inspires more confidence in the work from stakeholders.&lt;/p&gt;
&lt;p&gt;Let's build software that's fit for purpose, not just good enough.&lt;/p&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 21 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/good-enough-vs-fit-for-purpose/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>I'm a Coin Boy, Too</title><link>https://taylor.town/coin-boys</link><description>My crystal ball shattered in my twenties, but the coin offers faith to the faithless.</description><author>taylor.town</author><pubDate>Mon, 21 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/coin-boys</guid></item><item><title>CLI tip 32: text processing between two files with GNU awk</title><link>https://learnbyexample.github.io/tips/cli-tip-32/</link><description>&lt;p&gt;&lt;code&gt;awk&lt;/code&gt; is handy to compare records and fields between two or more files. The &lt;em&gt;key&lt;/em&gt; features used in the solution below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For two files as input, &lt;code&gt;NR==FNR&lt;/code&gt; will be &lt;code&gt;true&lt;/code&gt; only when the first file is being processed&lt;/li&gt;
&lt;li&gt;&lt;code&gt;next&lt;/code&gt; will skip rest of the script and fetch the next record&lt;/li&gt;
&lt;li&gt;&lt;code&gt;a[$0]&lt;/code&gt; by itself is a valid statement. It will create an uninitialized element in array &lt;code&gt;a&lt;/code&gt; with &lt;code&gt;$0&lt;/code&gt; as the key (assuming the key doesn't exist yet)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$0 in a&lt;/code&gt; checks if the given string (&lt;code&gt;$0&lt;/code&gt; here) exists as a key in the array &lt;code&gt;a&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ cat colors_1.txt
&lt;/span&gt;&lt;span&gt;teal
&lt;/span&gt;&lt;span&gt;light blue
&lt;/span&gt;&lt;span&gt;green
&lt;/span&gt;&lt;span&gt;yellow
&lt;/span&gt;&lt;span&gt;$ cat colors_2.txt
&lt;/span&gt;&lt;span&gt;light blue
&lt;/span&gt;&lt;span&gt;black
&lt;/span&gt;&lt;span&gt;dark green
&lt;/span&gt;&lt;span&gt;yellow
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# common lines
&lt;/span&gt;&lt;span&gt;$ awk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'NR==FNR{a[$0]; next} $0 in a'&lt;/span&gt;&lt;span&gt; colors_1.txt colors_2.txt
&lt;/span&gt;&lt;span&gt;light blue
&lt;/span&gt;&lt;span&gt;yellow
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# lines from colors_2.txt not present in colors_1.txt
&lt;/span&gt;&lt;span&gt;$ awk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'NR==FNR{a[$0]; next} !($0 in a)'&lt;/span&gt;&lt;span&gt; colors_1.txt colors_2.txt
&lt;/span&gt;&lt;span&gt;black
&lt;/span&gt;&lt;span&gt;dark green
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="warning" src="/images/warning.svg" /&gt; Note that the &lt;code&gt;NR==FNR&lt;/code&gt; logic will fail if the first file is empty, since &lt;code&gt;NR&lt;/code&gt; wouldn't get a chance to increment. You can set a flag after the first file has been processed to avoid this issue. See &lt;a href="https://unix.stackexchange.com/a/237110/109046"&gt;this unix.stackexchange thread&lt;/a&gt; for more workarounds.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# no output
&lt;/span&gt;&lt;span&gt;$ awk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'NR==FNR{a[$0]; next} !($0 in a)' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;dev&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;null &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;(seq &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# gives the expected output
&lt;/span&gt;&lt;span&gt;$ awk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'!f{a[$0]; next} !($0 in a)' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;dev&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;null f=&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;(seq &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here's an example of comparing specific fields instead of whole lines. When you use a &lt;code&gt;,&lt;/code&gt; separator between strings to construct the array key, the value of &lt;code&gt;SUBSEP&lt;/code&gt; is inserted. This special variable has a default value of the non-printing character &lt;code&gt;\034&lt;/code&gt; which is usually not used as part of text files.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ cat marks.txt
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Dept    Name    Marks
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;ECE     Raj     &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;53
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;ECE     Joel    &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;72
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;EEE     Moi     &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;68
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;CSE     Surya   &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;81
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;EEE     Tia     &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;59
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;ECE     Om      &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;92
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;CSE     Amy     &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;67
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ cat dept_name.txt
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;EEE Moi
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;CSE Amy
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;ECE Raj
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ awk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'NR==FNR{a[$1,$2]; next} ($1,$2) in a'&lt;/span&gt;&lt;span&gt; dept_name.txt marks.txt
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;ECE     Raj     &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;53
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;EEE     Moi     &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;68
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;CSE     Amy     &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;67
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/learn_gnuawk"&gt;CLI text processing with GNU awk&lt;/a&gt; ebook.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Mon, 21 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/cli-tip-32/</guid></item><item><title>Setting up a micro PC as a Linux server</title><link>https://avikdas.com/2023/08/21/setting-up-a-micro-pc-as-a-linux-server.html</link><description/><author>Avik Das</author><pubDate>Mon, 21 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://avikdas.com/2023/08/21/setting-up-a-micro-pc-as-a-linux-server.html</guid></item><item><title>SciFi Author as Product Owner</title><link>https://medium.com/@k0ryk/scifi-author-as-product-owner-88efbcfae7a1?source=rss-3cf45d4f3676------2</link><description>&lt;h4&gt;Fictional products we can make today&lt;/h4&gt;&lt;p&gt;“Twenty Thousand Leagues Under the Sea” popularized submarines in the public’s collective imagination, but submarines had been written about for centuries before this, since the late 1500’s. When the development of technology was slow, so was the development of science fiction. In the past 60 years, the genre has evolved incredibly. Verne’s work is considered to be one of the first examples of speculative fiction predicting major technology. I wonder if there are any fictional inventions that could be made real today.&lt;/p&gt;&lt;p&gt;In this series, I want focus on speculative fiction sometimes called hard sci fi, but I do not want to try to nail down an exact definition. Some of my favorite hard sci fi books are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;“Anathem” by Neal Stephenson&lt;/li&gt;&lt;li&gt;“Seveneves” by Neal Stephenson&lt;/li&gt;&lt;li&gt;“Pushing Ice” by Alastair Reynolds&lt;/li&gt;&lt;li&gt;“The Martian” by Andy Weir&lt;/li&gt;&lt;li&gt;“The Three-Body Problem” by Liu Cixin&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Designer vs Inventor&lt;/h4&gt;&lt;p&gt;Hard sci fi is not a requirement for us to be able to base real-world products off of plot devices. Many times the authors are not the first to come up with the idea. Authors package these ideas up and deliver them to the readers minds. Heinlein introduces the idea of a hydraulic bed in “Stranger in a Strange Land.” In his 1980s collection of essays, “Expanded Universe,” Heinlein talks about designing the waterbed:&lt;/p&gt;&lt;blockquote&gt;“I designed the waterbed during years as a bed patient in the middle thirties; a pump to control water level, side supports to permit one to float rather than simply lying on a not very soft water filled mattress. Thermostatic control of temperature, safety interfaces to avoid all possibility of electric shock, waterproof box to make a leak no more important than a leaky hot water bottle rather than a domestic disaster, calculation of floor loads (important!), internal rubber mattress and lighting, reading, and eating arrangements — an attempt to design the perfect hospital bed by one who had spent too damn much time in hospital beds.”&lt;/blockquote&gt;&lt;p&gt;The first waterbeds “were probably goatskins filled with water, used in Persia more then 3,600 years ago&lt;a href="http://www.technovelgy.com/ct/content.asp?Bnum=385"&gt;¹&lt;/a&gt;.” Heinlein designed the waterbed, but he was not the first to have a go at it. His rendition is much truer to our collective expectations of products in the modern era. It does not matter if these authors are first to come up with the ideas, what matters is their ability to make it possible. The whole point of speculative fiction (and product design) is taking what we have now and speculating realistice and acheivable futures.&lt;/p&gt;&lt;h4&gt;Investigating Products&lt;/h4&gt;&lt;p&gt;I am pulling three products from some of my favorite science fiction to evaluate and design. I am going to pretend that these are actual product owners, and that any of my questions can be answered or inferred by the text. Some features will likely be sacrificed for delivery. My output is going to be similar to what I would do on an initial pass with a whiteboard.&lt;/p&gt;&lt;p&gt;The products we will be investigating:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Part 1: The Illustrated Primer&lt;/strong&gt; from “The Diamond Age” by Neal Stephenson&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Part 2: Gamma Level Simulations&lt;/strong&gt; from The Revelation Space Series by Alistair Reynolds&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Part 3: AI Tablet&lt;/strong&gt; from The Mars Series by Kim Stanley Robinson&lt;/li&gt;&lt;li&gt;Any others? Leave a comment.&lt;/li&gt;&lt;/ul&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1020/1*csJxGh7vIsh30kbfW2ZZjw.png" /&gt;&lt;figcaption&gt;Part 1. The illustrated Primer. Source: Dall-E&lt;/figcaption&gt;&lt;/figure&gt;&lt;h3&gt;Part 1: The Diamond Age: Or, A Young Lady’s Illustrated Primer&lt;/h3&gt;&lt;p&gt;The first product we are going to investigate is the Illustrated Primer from “The Diamond Age.” In my opinion, the book fits into the cyberpunk genre. The world is not quite dystopic, but it is very real and gritty. The story’s protagonist, Nell, starts as a young girl as the novel is a coming of age story. Early in the story, she receives the product we will be talking about, a book called “A Young Lady’s Illustrated Primer.” The physical device may be a bit beyond our reach for modern-day technology. It looks and feels like a book — large and leather bound. Its pages look like normal pages, but are essentially colored e-ink touch screens. The purpose of the book is to guide the reader, assumed to be a young girl. It does this through generative AI. The book incorporates aspects of Nell’s life into storybook lessons. The book builds an ongoing fairy tail narrative to embed the lessons in.&lt;/p&gt;&lt;p&gt;The story does not have good generative voice technology. All the lessons Nell receives in her book are voice acted by real people called “ractors.” They’re an important part of the story, but at this point I have good news for my Product Owner: “We can cut out a lot of cost by using voice gen AI, it’s getting pretty good.” The book was not meant for mass production, which is why it was set up this way. Not our product, we need to scale.&lt;/p&gt;&lt;h4&gt;Design&lt;/h4&gt;&lt;p&gt;Alright, a magic book, we got this. Let’s set up some constraints:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Hardware.&lt;/strong&gt; This is going to ship as a mobile app meant for tablets. The paper-thin foldable color e-ink double-sided screens in the product spec can be investigated by the hardware team next quarter.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Omniscience&lt;/strong&gt;. In the novel, the primer can detect a lot of what’s going on in Nell’s surroundings. We might get in trouble on the App store if we are secretly recording children’s days. For this, let’s assume that we can get some data from integrations with other apps (map applications, social networks, calendars etc.).&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Offline.&lt;/strong&gt; We cannot support all of the features with no internet connection. We will be able to support lessons with limited interaction while offline.&lt;/li&gt;&lt;/ul&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LEoAoua71hIs3wWp2NmX3g.png" /&gt;&lt;figcaption&gt;Frontend interaction design. Source: Me&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;strong&gt;Components&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;IOS and Android app&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;API Gateway&lt;/strong&gt; for two major APIs: Interactions and Lessons&lt;/li&gt;&lt;li&gt;Major APIs are split into &lt;strong&gt;microservice architecture&lt;/strong&gt; (will dig into these in a moment).&lt;/li&gt;&lt;li&gt;&lt;strong&gt;OLTP datastore&lt;/strong&gt; — for lessons, interactions, frequently used data&lt;/li&gt;&lt;li&gt;&lt;strong&gt;OLAP datastore&lt;/strong&gt; — knowledge graph, for life events, relationships, user’s proficiency and understanding of lesson concepts&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Data Pipeline&lt;/strong&gt; — ingests events, relationships, interactions into the knowledge graph&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Analytics Engine&lt;/strong&gt; — OLAP workloads. Use the knowledge graph to generate lesson trees, tune models, reinforcement learning.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;Mobile App&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;What is the mobile app besides a very complex display layer? It can provide a lot of functionality to offload compute from our APIs. Here are a few examples:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Who is around? Facial detection, recognition&lt;/li&gt;&lt;li&gt;What is around? Computer vision &amp;amp; object detection&lt;/li&gt;&lt;li&gt;What’s going on? Sound/signal analysis, facial mood recognition, body language&lt;/li&gt;&lt;li&gt;What’s being said? Voice to text&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The main goals of the app are to gather data for the knowledge graph and provide an intuitive and stable interface for the end user. The app must be extensible enough for our lesson-building AI models to be able to generate a large variety of content&lt;/p&gt;&lt;p&gt;The capabilities of the mobile app’s display layer have to be extremely flexible. The lesson data model is in the form of a tree. A single lesson can branch into many different side paths, and converge depending on the needs of the user.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Microservices&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Why microservice architecture for this? There are a lot of functions that many need to scale separately. It would also be useful to delegate each of these microservices to different teams for parallel delivery.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Voice generation microservice — utilized by interactions API for on-demand voice and the Analytics Engine uses it to generate voices for lessons.&lt;/li&gt;&lt;li&gt;Knowledge API — OLTP access to the knowledge graph for looking up people, places, things, past events.&lt;/li&gt;&lt;li&gt;Interaction API — Uses multi-agent LLM network for real-time interaction with the user during lessons and outside of them.&lt;/li&gt;&lt;li&gt;Telemetry API —For ingestion of data used for analytic purposes, as well as building out the knowledge graph&lt;/li&gt;&lt;/ul&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/proxy/1*FIlHAEPo8p_H8s1ZxHXTAA.png" /&gt;&lt;figcaption&gt;A visualization of our data model. Source: Dall-E&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;strong&gt;Data Pipeline&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;This can start out rather generic. We have an ingestion microservice API, and we need an extensible pub/sub system where we can run data enrichment workloads before it hits the data warehouse and the knowledge graph. One large part of the data pipeline will be the consumers that build out the knowledge graph. Any sort of detail about the user’s life or events will be added to its appropriate place in the graph. This will have to be massively scalable, because individual users will be able to trigger large amounts of graph updates. Some of the nodes in the knowledge graphs may become supernodes and need more complex data modeling to stay performant.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Knowledge Graph&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;We would use a highly scalable performant Graph Database Management System (like neo4j). The data model would start out simple, and evolve. I would want to represent time, people and events.&lt;/p&gt;&lt;p&gt;The foundation for the knowledge graph would be a data lifecycle that would be maintained by the analytics engine. It would use a bucketing approach for slices of time. More recent slices of time would be smaller, more distant time slices would be larger. We might bucket today in a series of hours, the month in a series of days, and three years ago in a series of months or seasons.&lt;/p&gt;&lt;p&gt;Events are the basis of the data model. The time slices are just ways to organize and access events. An event may start out as multi nodes, something like this (in neo4j’s Cypher):&lt;/p&gt;&lt;pre&gt;CREATE (event1:Event {event: &amp;quot;woke up&amp;quot;, location: &amp;quot;home&amp;quot;})&lt;br /&gt;CREATE (event2:Event {event: &amp;quot;ate breakfast&amp;quot;, location: &amp;quot;home&amp;quot;})&lt;br /&gt;CREATE (event1)-[:THEN]-&amp;gt;(event2)&lt;br /&gt;CREATE (person1:Person {name: &amp;quot;mom&amp;quot;})&lt;br /&gt;CREATE (person1)-[:PARTICIPATED]-&amp;gt;(event2)&lt;/pre&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zx_-stsx114OfsIMw8D89g.png" /&gt;&lt;figcaption&gt;The graph created from the queries. Source: Me&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;The events “woke up” and “ate breakfast” maybe would later be merged by the analytics engine into “breakfast.” The query example also shows how people could be added to different events. I have a lot of ideas of how we could implement this, but will wait until I hear back from the PO.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Analytics Engine&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;The analytics engine is responsible for the data lifecycle of the knowledge graph. As events fall further into the past, they are merged and compressed. In addition, it will modify the relationships between people and events. Eating breakfast will be associated with “mom.” Events always point to people who participated in them. However, some people will have events associated with them, things we remember or due to the sparseness of the relationship. The analytics engine will regularly perform these tweaks to the data in order to keep it performant.&lt;/p&gt;&lt;p&gt;The analytics engine is also responsible for building lessons. The engine takes in recent life events, the history of lessons so far, and standard education expectations for the user’s demographic. Taking all this in, it maintains a lesson graph. The graph represents all the paths that lessons could currently take. Each lesson is a set of interactive information hooked up to triggers. Sometimes those triggers bring the user to other lessons. The idea is to have something extensible and complete, each lesson’s assets are fully generated by generative AI and downloaded onto the client so that they can be used offline.&lt;/p&gt;&lt;h3&gt;Final Thoughts&lt;/h3&gt;&lt;p&gt;Is this whiteboarding useful? Your mileage may vary. I think it’s completely possible to create some of these products. The young lady’s primer would probably be one of the hardest ones to bring to market, because it has a lot of data and privacy violations baked into the design. There are many creations we have the components for, but they will never come to fruition anywhere but our minds. Thanks for reading.&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;a href="http://www.technovelgy.com/ct/content.asp?Bnum=385"&gt;http://www.technovelgy.com/ct/content.asp?Bnum=385&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;img alt="" height="1" src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=88efbcfae7a1" width="1" /&gt;</description><author>Stories by Kory Kirk on Medium</author><pubDate>Sun, 20 Aug 2023 22:43:11 GMT</pubDate><guid isPermaLink="true">https://medium.com/@k0ryk/scifi-author-as-product-owner-88efbcfae7a1?source=rss-3cf45d4f3676------2</guid></item><item><title>The Missing Screwtape Letters from Academia</title><link>https://bastian.rieck.me/blog/2023/screwtape/</link><description>&lt;p&gt;My dearest Wormwood,&lt;/p&gt;
&lt;p&gt;It warms my heart to hear that you were able to obtain a post as
a reviewer! Humans, in particular academics, care so &lt;em&gt;much more&lt;/em&gt;
about prestige than they want to admit. Well done!&lt;/p&gt;
&lt;p&gt;Allow me to provide you with some avuncular words of advice such
that you may, in time, ensure that the Patient becomes ours. Let
me start with how you should write your reviews. Make sure to be
always strikingly polite, at least at the start of the review. I
can attest to the fact how nicely this serves to provide a sense
of security (false, of course!) to the Patient. Indeed, you
want them to think that they received a glowing review. Then, as
your text progresses, show your claws but be apologetic about it
as much as you can. Use phrases such as &amp;lsquo;I would &lt;em&gt;truly&lt;/em&gt; like to
accept this work, but, alas, there are too many issues that make
it impossible for me to endorse it for publication.&amp;rsquo;&lt;/p&gt;
&lt;p&gt;Moreover, ensure to mention issues that are not strictly crucial
for the paper. That way, you provide a way for the Patient to be
initially hopeful. &amp;lsquo;Surely I can convince that reviewer with our
response&amp;rsquo;, they will think. This is your chance to &lt;strong&gt;push them&lt;/strong&gt;
to the brink of despair: graciously concede some points during a
discussion, while constantly raising others. Strive, if you can,
to discuss minuscule details, always shifting the focus from the
large issues to the small. Over time, if done well, this will be
a large annoyance to the Patient. Notice that this stratagem can
only work if you strictly keep your cool! You want to be seen as
a polite, apologetic reviewer. Always pepper your responses with
empty phrases like &amp;lsquo;Much to my chagrin&amp;rsquo; or prefix your responses
to the authors by &amp;lsquo;Unfortunately.&amp;rsquo;&lt;/p&gt;
&lt;p&gt;The Patient should still feel hope throughout the process! Then,
finally, when it comes to the decision, make sure to never move,
not even an inch! Stick with your original assessment, and, in a
discussion with the &lt;em&gt;other&lt;/em&gt; reviewers, cite your many queries as
ample evidence that the work is &lt;em&gt;obviously&lt;/em&gt; not ready yet.&lt;/p&gt;
&lt;p&gt;Repeat this procedure multiple times, and the Patient will be an
outstanding candidate for us.&lt;/p&gt;
&lt;p&gt;Trust me on this.&lt;/p&gt;
&lt;p&gt;Your affectionate uncle,&lt;br /&gt;
  Screwtape&lt;/p&gt;
&lt;p&gt;PS: Another strategy, for those of us that are of a more lazy bent, is
to never reply in discussions until the &lt;strong&gt;very last minute.&lt;/strong&gt; Then, as
you jump on your keyboard, raise issue over issue, making sure that no
mortal can address all of them. This leaves the patient fuming, but my
personal opinion is that this approach lacks a certain decorum. It may
be just as effective in the end, though!&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;My dear and caring uncle Screwtape,&lt;/p&gt;
&lt;p&gt;I am not sure I have the stomach for this. Even for us, this seems to be
particularly cruel. I will think and reflect on your words, and I really
hope to be able to muster these tools at some point.&lt;/p&gt;
&lt;p&gt;Your obedient nephew,&lt;br /&gt;
  Wormwood&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;This post is dedicated to all those that take their reviewing and
paper-writing seriously. With apologies to &lt;a href="https://en.wikipedia.org/wiki/The_Screwtape_Letters"&gt;C.S. Lewis&lt;/a&gt;.&lt;/p&gt;</description><author>Ecce Homology on Bastian Grossenbacher Rieck's personal homepage</author><pubDate>Sun, 20 Aug 2023 15:00:00 GMT</pubDate><guid isPermaLink="true">https://bastian.rieck.me/blog/2023/screwtape/</guid></item><item><title>Old Books, Strategy, and Tactics</title><link>https://jonpauluritis.com/articles/books-strategy-tactics/</link><description>&lt;p&gt;Though it's fading recently, a major pursuit of the front half of my life was trying to become &lt;em&gt;prematurely&lt;/em&gt; wise.&lt;/p&gt;
&lt;p&gt;I did the &amp;quot;work&amp;quot;: I studied great humans, read all of the classics (minus the Brothers Karamazov), read books acclaimed for their wisdom, searched out wise people to be around, et cetera, et cetera. But it didn't have the effect that I thought it would.&lt;/p&gt;
&lt;p&gt;I don't think my time was misspent. I crush it with anecdotes at cocktail parties, I've made friends by discussing books with interesting people... I always have a great quote for the situation and usually a witty one.&lt;/p&gt;
&lt;p&gt;It's useful stuff, but nothing that will make me wealthy, help me create beautiful art, help me live longer or more fully, or any of the things worth doing during our short stint in this world. Put another way: &lt;strong&gt;Studying wisdom didn't make me wise.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Old Programming Books&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;These programming books are one of the weirder aspects of my current profession (software engineering)&lt;/strong&gt;. Go to pretty much any library in the United States and there will be a small section of books on general IT topics, like using a computer, doing web design, and programming.&lt;/p&gt;
&lt;p&gt;From what I've seen these books are always horrifically dated, frequently covering dead/dying languages, ancient frameworks, and software that doesn't even exist anymore. Bookstores are only slightly better- they have the new books but by the time that they're published things have already changed.&lt;/p&gt;
&lt;p&gt;Every time I see the library section or the programming section in a bookstore... I'm always asking myself: &amp;quot;What gives? &lt;strong&gt;Why would someone dedicate so much time to crafting a book that will be obsolete so soon?&lt;/strong&gt;&amp;quot; These books seem to be the opposite of the books I read all of my youth - so devoid of &amp;quot;wisdom&amp;quot; that they can't be read more than 5 years after they were written.&lt;/p&gt;
&lt;p&gt;Years later I've come to realize that &lt;strong&gt;books are written for different reasons, for different audiences, and there are even some books that aren't meant to be read&lt;/strong&gt;. BUT &lt;strong&gt;there is more wisdom in these obsolete books than I had realized&lt;/strong&gt; when I thought books were supposed to be for reading. As an example, Authors Nassim Nicholas Taleb and Ryan Holiday (and others) seemed to have picked up on this. Their writing careers are primarily re-writing 3000+ year old &amp;quot;wisdom&amp;quot; for modern audiences.&lt;/p&gt;
&lt;h2&gt;Wisdom not found in books&lt;/h2&gt;
&lt;p&gt;Here's a thing you can try. Think of the most successful or talented person that you know. Now go ask them how to become either as successful/talented as them (or more if possible). Listen to their answer. Chances are they either won't be able to explain it in any detail, that is, they won't be able to provide the steps to reproduce their success.&lt;/p&gt;
&lt;p&gt;Many of the successful people I know have difficulty explaining &amp;quot;how&amp;quot; they do it. 2 main reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Learning how to accomplish great things is complex. Becoming a great athlete, artist, business person, etc may be as complex as building a skyscraper or nuclear reactor, but it doesn't feel that way to the rest of us.&lt;/li&gt;
&lt;li&gt;Successful/talented people might not actually know how they do some of the things they do that are difficult.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Much wisdom can't be documented,&lt;/strong&gt; but why?&lt;/p&gt;
&lt;h2&gt;Strategy and Tactics&lt;/h2&gt;
&lt;p&gt;It took me way longer than I'd like to admit to figure this out, but I spent the vast majority of my life conflating wisdom, strategy, and tactics. It's an easy mistake to make and it's difficult to interpret the nuances. But here goes...&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Strategy will describe how to interact with the world at a high level.&lt;/strong&gt; People tend to avoid describing where they failed, fell short, or nuances they discovered when they are talking about things at a high level. They skip the little things. They avoid the details of the process, the surprises, or things that only an expert could execute.&lt;/p&gt;
&lt;p&gt;This makes sense - they are the narrator and they get to shape the way people experience their description. As an example: Saying that everyone is right-handed, is incorrect, but saying &amp;quot;pretty much everyone&amp;quot; is right-handed is accurate.&lt;/p&gt;
&lt;p&gt;Most books by virtue of the media form are typically strategy. The book would become outdated if it focused on tactics.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tactics are how to do a thing effectively.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Tactics change quickly because more effective tactics are discovered and counter-tactics are developed... But strategy is useless without tactics.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Wisdom is possessing a beyond-expert vocabulary of effective tactics&lt;/strong&gt; that will work in a given discipline at that time.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You need to know WHEN to use a tactic.&lt;/li&gt;
&lt;li&gt;You need to know when tactics are no longer effective.&lt;/li&gt;
&lt;li&gt;You need to be able to create more effective tactics to continue to be successful.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Wisdom is expensive. It requires resources, experimentation, trial and error when you find a tactic you need to retain it and know when to apply it.&lt;/p&gt;
&lt;h3&gt;Putting a bow on it&lt;/h3&gt;
&lt;p&gt;What are the take-aways? I think there are a lot of different take-aways so &amp;quot;to each their own&amp;quot; for what you can take away from this.&lt;/p&gt;
&lt;p&gt;For me, &amp;quot;strategy&amp;quot; might be over-rated since effective tactics are probably more valuable day-to-day. Would you rather be really good at 3 point shooting or know how to coach someone to be a good 3 point shooter? Would you rather be really good at making money or know how people make money? Not really a straight-forward answer, but I'd probably take &amp;quot;tactics.&amp;quot;&lt;/p&gt;
&lt;p&gt;Also, after coming up with this observation I'm less concerned about &amp;quot;wisdom&amp;quot; these days. Both acquiring it and/or the general concept. Don't take me the wrong way, it's definitely valuable... but I just want to focus on the underlying structure... in the words of Steve Martin: &lt;em&gt;&amp;quot;be so good that they can't ignore you,&amp;quot;&lt;/em&gt; or put another way, the journey is the destination.&lt;/p&gt;
&lt;p&gt;Lastly, I can't help but think that some extra existentialism has crept into my life around this topic. Even the wisest of us will still end up 6 feet under, so we should enjoy the journey - that predominantly means tactics.&lt;/p&gt;</description><author>JonPaulUritis.com</author><pubDate>Sun, 20 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jonpauluritis.com/articles/books-strategy-tactics/</guid></item><item><title>Two lines of shell script in 4 hours of custom Ansible module development</title><link>https://gist.github.com/mhitza/b6eb99314b45444e531ead74aa9ec406</link><description/><author>personal code attic</author><pubDate>Sun, 20 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://gist.github.com/mhitza/b6eb99314b45444e531ead74aa9ec406</guid></item><item><title>Cyberpunk 2077 review</title><link>https://burakku.com/blog/cyberpunk-2077-review/</link><description>&lt;p&gt;&lt;img alt="Cyberpunk 2077" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;My favourite game of the 2020s (so far).&lt;/p&gt;
&lt;p&gt;I've poured about 400 hours towards Cyberpunk 2077 across all the platforms but I'm still finding it quite hard to write a review of the game. Cyberpunk 2077 has famously been kind of a mess, so why have I dedicated so much of my time towards it? I'm not sure if I know the answer.&lt;/p&gt;
&lt;p&gt;I think one of the reasons why I like Cyberpunk 2077 is just the world. Night City is dense and grandiose stage that is essentially one of the main characters of the game with its overwhelming presence. There might be some issues when it comes to AI to make it appear like the city was full of real people going about their real lives akin to recent Grand Theft Auto games, it is still a sublime place to be in. I at least do not remember having as much fun exploring Los Santos in GTA V as I did when exploring the various alleys and roofs of Kabuki. Night City is called The City of Dreams and its design truly embodies it.&lt;/p&gt;
&lt;p&gt;What didn't feel that strong was traversing the world though. You can fast travel between points you have already discovered, but I used it sparingly because I actually wanted to spend time in Night City. Sprinting around the city was actually a really valid option for moving short distances and I probably used it more than you'd think. However, for longer distances, you really need some kind of a motorised device. And the car physics in Cyberpunk 2077 are not the greatest. I've definitely encountered worse driving games but this isn't Grand Theft Cyberpunk.&lt;/p&gt;
&lt;p&gt;However, I did discover the ultimate way to travel in Night City: the Yaiba Kusanagi CT-3X. While cars have a very funky handling model, zipping around on a motorcycle is actually super fun and the Kusanagi is the best of them. And in addition to being fun to drive, you can use the Kusanagi to go basically anywhere and cut through the Night City congestion. Really the only downside of the Kusanagi is that it's so much fun that it overshadows the rest of the vehicles. Kind of a shame, since the game is full of fun and cool vehicle designs. One of the cars even talks to you!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Yaiba Kusanagi CT-3X" src="kusanagi.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;While exploring that world, it also becomes pretty evident that Cyberpunk 2077 is a part of a larger franchise. There's a shocking amount of lore stuffed into the game. So much so that I imagine that only few players are actually going to go through all of it. But even if you're not going to be engaging with all of it, it still has the effect of making Cyberpunk 2077 feel like it's an actual world and not just shallow set dressing. I've definitely taken an interest in the Cyberpunk franchise after playing through 2077. If only I had some friends to play the TTRPG with.&lt;/p&gt;
&lt;p&gt;Another strength of the game is the writing. I feel like a lot of games these days have quite unnatural writing to them. My guess would be that they're trying to emulate the kind of "witty" writing that is present in current Hollywood blockbusters. Thankfully Cyberpunk 2077 doesn't feel like that even though they hired a Hollywood actor to appear in it. The dialogue seems a bit weird or otherworldly when compared to standard English though but I feel like that might be a desired effect. And I'm not referring to the specific 2077 slang words that are used. It would be kinda boring if people living in an alternative future spoke English exactly like people in our regular 2020 did.&lt;/p&gt;
&lt;p&gt;I quite enjoyed the cast of characters too. You cannot have a huge influence over the kind of a person V is but I liked her (female V forever). She also meshes well with your main sidekick that's with you for most of the game and I enjoyed the interactions between the two. There's also quite a large array of interesting secondary characters and even tertiary characters have some characterisation. The only characters that I hated, I hated because they were awful people that could only be fixed with a bullet to the skull, not because they were badly written characters. I guess the only issue I had with the characters was that the voice actors were often voicing several characters, so you might encounter a shopkeeper that sounds exactly like your girlfriend.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Johnny Silverhand" src="silverhand.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;The main story of the game is good although not necessarily amazing. I also feel like it's relatively short for a single-player roleplaying game. I think a main story only playthrough will only eat about 20 hours of your time. Perhaps it was kept short to give players a chance to finish it since relatively few people ever actually complete games. Personally I think I could've done with additional fleshing of the main story. I'm not sure if the Cyberpunk 2077 story is the most interesting story possible in the Cyberpunk universe, since there is quite a lot of material available, but it was interesting enough to make me want more of it.&lt;/p&gt;
&lt;p&gt;However, to complement the relative brevity of the main story, the game has quite a lot of optional content. There's questlines for the main secondary characters, various side missions, gigs by the local fixers, cyberpsychos to subjugate and requests to "keep the peace" (neutralise targets) from the police. And the optional content is much higher quality than many other open world games where the additional content is mainly just collectibles. Some of my favourite missions in the game are in fact optional. Even the police requests, which are the lowest effort side content in the game, still have some amount of setting and variation to them. None of optional content felt like a chore or a lazy collectible like in some other games. In fact, I might take a detour while traveling from one story mission to another to just clear out a couple of NCPD murder requests.&lt;/p&gt;
&lt;p&gt;The talent system allows quite personalised ways of playing as V. For my first playthrough, I went for a pretty generic jack of all trades gunner build that didn't really strike me in any way. After that I went for a more stealthy builds, which were much more to my liking. And on my fourth playthrough, I opted for a katana-wielding Sandevistan ninja build, which I really enjoyed. Never really tried out a serious gunner build nor have I tried a netrunner build, as those didn't seem as interesting. Maybe when I decide that it's time to complete Cyberpunk 2077 for a fifth time that I might experiment with those.&lt;/p&gt;
&lt;p&gt;&lt;img alt="V" src="v.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;The way that you specialise your V alters the core gameplay quite a bit, and since I've never tried out all of the possible ways to play the game, I can't comment on all of them. But I do generally like the gunplay, the melee combat and the stealth mechanics. Even though I never committed properly to be a gunner, I always found it to be satisfying enough. And I quite liked the combination of stealth and hacking abilities to be able to circumvent cameras and people. And the Sandevistan katana build was just visceral catharsis with the way you could overpower your opponents.&lt;/p&gt;
&lt;p&gt;I do find that the talent system does contribute to quite an uneven difficulty curve though. I always found the game to start off way harder than it ended, since at the start, you have so much worse gear, so much worse cybernetics and lacked all talents. When playing on the hard difficulty, it was quite easy to get yourself killed if you made any mistakes. And after you'd obtained your equipment and talents towards the end of the game, it was so much harder to die to a point of being a rare event. This is not exactly a rare effect with games built around equipment and talents, but for the sequel, I hope that CDPR flattens out the difficulty curve a bit.&lt;/p&gt;
&lt;p&gt;Beyond just trying out different sorts of builds, Cyberpunk 2077 offers pretty good replayability in general. I should know, I've played it through four times already! I'm pretty sure I've discovered something new every time I've played through the game, and not just because they've kept patching the game ever since it launched in 2020. There's always been something new to discover in the environments, some kind of a way to complete a quest that I hadn't tried before, or something completely else. And judging by the stuff I've encountered on YouTube and various websites, there's still stuff that I've never seen before. There's usually a couple of ways to complete any given quest, so you might opt to go for a guns blazing approach on your first try and for a stealthy approach on the second. The various builds also unlock new ways to approach some of the quests.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Night City" src="nightcity.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;The game is gorgeous too. I think this is still the apex of gaming graphics after two years of its release, and the system requirements really match that. Cranking the settings to their highest (ignoring path tracing because it's just a technology preview), I was able to get around 80 FPS at 1440p with an RTX 4090. Thankfully you can tweak the settings for a more optimised result on weaker systems, but this is really a game that benefits from beefy hardware. The beautiful graphics really just further enhance the beauty of Night City. Soundtrack of the game is also rather good, and I find myself listening to many of the tracks outside of the game too. Never Fade Away in particular is currently topping the charts on my Last.fm profile. I also loved the voice acting for female V, and really none of the cast really disappointed me with their performance.&lt;/p&gt;
&lt;p&gt;The game isn't perfect though, as was quite widely covered when the game launched. Far from it. There were so many different glitches and bugs when the game came out that it was hard to go ten minutes without encountering something. Thankfully there's been over two and a half years of effort put towards the game to polish it, but it's still not quite enough. Luckily the effort has managed to fix the worst game-breaking issues that were present in the beginning. The kind of bugs that would prevent progress or otherwise really make you miserable. But you will definitely still encounter people continuing to walk around after being decapitated or see a car plow through a wall. Bugs that break your immersion but not your progression. I imagine that Cyberpunk 2077 will never truly be jank-free.&lt;/p&gt;
&lt;p&gt;It also feels like they've managed to fix worst of the crashing too, since I think I only encountered a single crash on my new gaming PC. With my previous rig, I remember encountering several crashes in a single session. Not sure if that was an issue with the PC/installation I had at the time or if it was just the older versions. Hopefully newcomers to the game can enjoy it with only an occasional crash – since really no AAA game on the PC is truly 100% stable.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Bugs" src="bugs.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;I'm not really sure how relevant my review is going to be though. I'm writing this review just a few weeks before the new expansion, Phantom Liberty, comes out and overhauls the game. I haven't actually looked at a detailed list on all of the changes because I want to experience them with fresh eyes once they come out, but the little I've seen online suggests that they're actually doing quite a lot of changes. Hopefully the changes are just for the better and don't ruin anything that I currently love in the game. But since Cyberpunk 2077 has been quite the enjoyable experience for me, I do have a level of trust in CD Projekt Red to not muck it up at this point.&lt;/p&gt;
&lt;p&gt;Cyberpunk 2077 is kind of a rough product even after its polish, but one that has always captivated me and kept me playing, even though I still have a hard time explaining why. It's a fun game with a good story and writing, and it'd be hard to not recommend such a game.&lt;/p&gt;</description><author>ブラック</author><pubDate>Sun, 20 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/cyberpunk-2077-review/</guid></item><item><title>Concise explanations accelerate progress</title><link>https://stephango.com/concise</link><description>&lt;p&gt;If you want to progress faster, write concise explanations. Explain ideas in simple terms, strongly and clearly, so that they can be rebutted, remixed, reworked — or built upon.&lt;/p&gt;

&lt;p&gt;Concise explanations spread faster because they are easier to read and understand. The sooner your idea is understood, the sooner others can build on it.&lt;/p&gt;

&lt;p&gt;Concise explanations accelerate decision-making. They help everyone understand the idea and decide whether to agree with it or not.&lt;/p&gt;

&lt;p&gt;Concise explanations make ideas useful. One idea can more easily be combined with another idea to form a third idea.&lt;/p&gt;

&lt;p&gt;Concise explanations work at every scale. From your own thinking, to the progress of an entire organization, community, or civilization.&lt;/p&gt;

&lt;p&gt;Leadership is built on concise explanations. Without concise explanations you have no foundation to build on.&lt;/p&gt;</description><author>Steph Ango</author><pubDate>Sun, 20 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://stephango.com/concise</guid></item><item><title>A Powerful Generic TypeScript Function for Generating Valuable RAG Texts</title><link>https://chrisfrew.in/blog/a-powerful-generic-typescript-function-for-generating-valuable-rag-texts/</link><description>In a Nutshell: Given X, what’s the probability of Y occurring?</description><author>Chris' Full Stack Blog RSS Feed</author><pubDate>Sun, 20 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://chrisfrew.in/blog/a-powerful-generic-typescript-function-for-generating-valuable-rag-texts/</guid></item><item><title>2023–08–20: QuartzPro 64</title><link>https://xnux.eu/log/#093</link><author>megi's PinePhone Development Log</author><pubDate>Sun, 20 Aug 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#093</guid></item><item><title>HN Traffic</title><link>https://thraxil.org//users/anders/posts/2023/08/19/hn-traffic</link><description>&lt;p&gt;
The other day, a &lt;a href="https://thraxil.org/users/anders/posts/2023/08/05/on-to-elixir"&gt;recent post&lt;/a&gt; here made it to the front page of &lt;a href="https://news.ycombinator.com"&gt;Hacker News&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;
  &lt;img alt="HN" src="https://d2f33fmhbh7cs9.cloudfront.net/image/54bfc70950fc48fea069fc1f752e72383fa50549/1000w/image.png" /&gt;
&lt;/p&gt;
&lt;p&gt;
I don’t know if that still counts as a huge amount of traffic, but there was definitely a spike:&lt;/p&gt;
&lt;p&gt;
  &lt;img alt="HTTP Status Codes" src="https://d2f33fmhbh7cs9.cloudfront.net/image/85379b37ca24b26b3b8f004904a104bfb30c8f09/1000w/image.png" /&gt;
&lt;/p&gt;
&lt;p&gt;
(All the graphs in here are over the same 24 hour period).&lt;/p&gt;
&lt;p&gt;
I thought it might be interesting to show how the site, built on Elixir and the Phoenix web framework with basically zero attention paid to caching, scaling, or performance beyond what Phoenix does out of the box, performs when dealing with a reasonably large amount of traffic.&lt;/p&gt;
&lt;p&gt;
I run on &lt;a href="https://fly.io/"&gt;Fly.io&lt;/a&gt;, with the site on a 256MB Firecracker VM (their smallest, and still on the free tier). The PostgreSQL database is shared with a dozen other applications and also runs on a 256MB VM.&lt;/p&gt;
&lt;p&gt;
There were a couple spikes in response time, but those actually seem pretty common for the site in general and are mostly from deployments rather than changes in traffic:&lt;/p&gt;
&lt;p&gt;
  &lt;img alt="HTTP Response Times" src="https://d2f33fmhbh7cs9.cloudfront.net/image/edebc60b220f419b788eed30161815a0a35d4236/1000w/image.png" /&gt;
&lt;/p&gt;
&lt;p&gt;
For the most part, response times didn’t really change when it was getting the HN traffic spike.&lt;/p&gt;
&lt;p&gt;
Load average doesn’t really show much of a change either:&lt;/p&gt;
&lt;p&gt;
  &lt;img alt="App Load Avg" src="https://d2f33fmhbh7cs9.cloudfront.net/image/5e3b43f87dd49b3f39789cc732f083e1ca968dbf/1000w/image.png" /&gt;
&lt;/p&gt;
&lt;p&gt;
Memory usage went up &lt;em&gt;slightly&lt;/em&gt; but never even came close to being a problem:&lt;/p&gt;
&lt;p&gt;
  &lt;img alt="App Memory Usage" src="https://d2f33fmhbh7cs9.cloudfront.net/image/42239a5617003dfcd0a2817adf57bd98c396a4cc/1000w/image.png" /&gt;
&lt;/p&gt;
&lt;p&gt;
Fly.io spins up more VMs as needed (I’m not actually sure what load/events/whatever trigger that scaling) and it looks like maybe that happened a bit more often during the spike:&lt;/p&gt;
&lt;p&gt;
  &lt;img alt="VM Concurrency" src="https://d2f33fmhbh7cs9.cloudfront.net/image/14c6f95d5e5ef12fac929bd864c7434cb9bd78da/1000w/image.png" /&gt;
&lt;/p&gt;
&lt;p&gt;
PostgreSQL handled things like a champ too. No real noticable load or memory usage over my usual baseline:&lt;/p&gt;
&lt;p&gt;
  &lt;img alt="DB Load Avg" src="https://d2f33fmhbh7cs9.cloudfront.net/image/58d286f074eecb809c593995cef5e26dcc52b833/1000w/image.png" /&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;img alt="DB Memory Usage" src="https://d2f33fmhbh7cs9.cloudfront.net/image/5924137bd53804f4df55c990606fc75c02ca67a2/1000w/image.png" /&gt;
&lt;/p&gt;
&lt;p&gt;
I posted a few of these in a Slack channel while it was happening and I feel like I should probably include this reaction:&lt;/p&gt;
&lt;p&gt;
  &lt;img alt="graphs" src="https://d2f33fmhbh7cs9.cloudfront.net/image/7d8db5129fc1d1e8a1df04358a302c9fab4d4305/full/image.png" /&gt;
&lt;/p&gt;</description><author>Thraxil</author><pubDate>Sat, 19 Aug 2023 16:49:56 GMT</pubDate><guid isPermaLink="true">https://thraxil.org//users/anders/posts/2023/08/19/hn-traffic</guid></item><item><title>The B2B product doom spiral</title><link>https://ernest.oppet.it/2023/08/19/the-b2b-product-doom-spiral/</link><description>Don't try this at home!</description><author>Ernest Oppetit</author><pubDate>Sat, 19 Aug 2023 15:29:13 GMT</pubDate><guid isPermaLink="true">https://ernest.oppet.it/2023/08/19/the-b2b-product-doom-spiral/</guid></item><item><title>Visualizing Bookworm</title><link>http://evantravers.com/articles/2023/08/19/visualizing-bookworm/</link><description>&lt;p&gt;&lt;img alt="" src="https://images.evantravers.com/articles/2023/08/livebook.png" /&gt;&lt;/p&gt;&lt;pre class="lumis"&gt;&lt;code class="language-elixir" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;Mix&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;:req&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;&amp;quot;~&amp;gt; 0.3.0&amp;quot;&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;:floki&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;&amp;quot;~&amp;gt; 0.34.0&amp;quot;&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;:kino_explorer&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;&amp;quot;~&amp;gt; 0.1.4&amp;quot;&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;:libgraph&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;&amp;quot;~&amp;gt; 0.16.0&amp;quot;&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;!-- livebook:{"output":true} --&gt;
&lt;h2&gt;&lt;a class="anchor" href="#-why" id="-why"&gt;&lt;/a&gt;🤔 Why?&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://bookworm.fm/"&gt;Bookworm&lt;/a&gt; is one of my favorite podcasts. Like many listeners I use it as a filter to find quality books to add to my queue. I love how the hosts don't just read but engage with the material, holding each other accountable with takeaways and challenges.&lt;/p&gt;
&lt;p&gt;A recurring joke in Bookworm is how many times they refer to &lt;a href="https://bookworm.fm/42/"&gt;Ep. 42, How To Read a Book by Mortimer Adler&lt;/a&gt;. The last few times they've made that joke I've wanted to actually visualize that graph. I know Mike loves Obsidian, I love Elixir and have been learning &lt;a href="https://livebook.dev/"&gt;Livebook&lt;/a&gt;… let's play!&lt;/p&gt;
&lt;p&gt;(Livebook is a markdown-based workbook system (similar to Jupyter notebooks) that uses erlang/elixir. This blog post is mostly written in Livebook and ran in Livebook. I exported the content and output, added some screenshots, and here we are! You could actually take the markdown source of this post and open it in Livebook. Pretty cool.)&lt;/p&gt;
&lt;h2&gt;&lt;a class="anchor" href="#-scraping" id="-scraping"&gt;&lt;/a&gt;🎣 Scraping&lt;/h2&gt;
&lt;p&gt;The first thing is to scrape the pages for processing and evaluation. One lovely thing about Livebook is that if you structure each code block separately, it intelligently memoizes each variable and only re-evaluates them if the structure changes.&lt;/p&gt;
&lt;p&gt;Previously, I had used &lt;a href="https://github.com/edgurgel/httpoison"&gt;HTTPoison&lt;/a&gt; to do some downloading, but my friend &lt;a href="http://megalithic.io"&gt;Seth&lt;/a&gt; has recommended &lt;a href="https://github.com/wojtekmach/req"&gt;Req&lt;/a&gt;, so let's give that a whirl.&lt;/p&gt;
&lt;p&gt;Adding the following to the setup:&lt;/p&gt;
&lt;!-- livebook:{"force_markdown":true} --&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-elixir" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;Mix&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;:req&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;&amp;quot;~&amp;gt; 0.3.0&amp;quot;&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now I can see if I can hit the opening page:&lt;/p&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-elixir" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;Req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;quot;https://bookworm.fm/&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;!-- livebook:{"output":true} --&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-elixir" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;&amp;quot;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&amp;lt;html lang=&lt;/span&gt;&lt;span&gt;\&amp;quot;&lt;/span&gt;&lt;span&gt;en-US&lt;/span&gt;&lt;span&gt;\&amp;quot;&lt;/span&gt;&lt;span&gt; xmlns:og=&lt;/span&gt;&lt;span&gt;\&amp;quot;&lt;/span&gt;&lt;span&gt;http://ogp.me/ns#&lt;/span&gt;&lt;span&gt;\&amp;quot;&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; &amp;lt;&amp;gt; …
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Success! Let's be a little more intentional.&lt;/p&gt;
&lt;p&gt;Bookworm's website is structured logically. Every episode will be in the form &lt;code&gt;https://bookworm.fm/1/&lt;/code&gt; up to &lt;code&gt;https://bookworm.fm/&amp;lt;current&amp;gt;/&lt;/code&gt;. The most recent post is the top &lt;code&gt;&amp;lt;h2&amp;gt;&lt;/code&gt; on the page.&lt;/p&gt;
&lt;!-- livebook:{"break_markdown":true} --&gt;
&lt;p&gt;Now I can hone the CSS selector I need to get the most current link. What's really nice (again) about Livebook is that as I rerun the code over and over… I'm not pummeling my internet friends' server with requests. Livebook's cells have localized and memoized all the variables.&lt;/p&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-elixir" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;most_recent_episode_number&lt;/span&gt; &lt;span&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;Req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;url: &lt;/span&gt;&lt;span&gt;&amp;quot;https://bookworm.fm/&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Floki&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parse_document!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Floki&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;quot;h2 a&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;List&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;first&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Floki&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;attribute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;quot;href&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;List&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;first&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;String&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;trim: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;List&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;last&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;String&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;to_integer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;!-- livebook:{"output":true} --&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-elixir" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;176&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;☝️ This is a super gross way to do this, but I can fix it later.&lt;/p&gt;
&lt;p&gt;Anyway. Excelsior!&lt;/p&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-elixir" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;raw_episodes&lt;/span&gt; &lt;span&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;Enum&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;..&lt;/span&gt;&lt;span&gt;most_recent_episode_number&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;fn&lt;/span&gt; &lt;span&gt;num&lt;/span&gt; &lt;span&gt;-&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;url&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;&amp;quot;https://bookworm.fm/&lt;/span&gt;&lt;span&gt;#&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;num&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;/&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;%&lt;/span&gt;&lt;span&gt;&amp;amp;lbrace&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;# going to be important later! (basically the private key for the episode)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;url: &lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;request: &lt;/span&gt;&lt;span&gt;Req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;url: &lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;&amp;amp;rbrace&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;end&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;!-- livebook:{"output":true} --&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-elixir" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;%&lt;/span&gt;&lt;span&gt;&amp;amp;lbrace&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;request: &lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;Req.Response&lt;/span&gt;&lt;span&gt;&amp;amp;lbrace&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;status: &lt;/span&gt;&lt;span&gt;200&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;headers: &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;        &lt;span&gt;&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;&amp;quot;connection&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;&amp;quot;keep-alive&amp;quot;&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;        &lt;span&gt;&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;&amp;quot;content-length&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;&amp;quot;13177&amp;quot;&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;        …
&lt;/div&gt;&lt;div class="line"&gt;        &lt;span&gt;&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;&amp;quot;x-fw-type&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;&amp;quot;FLYWHEEL_BOT&amp;quot;&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;body: &lt;/span&gt;&lt;span&gt;&amp;quot;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&amp;lt;html lang=&lt;/span&gt;&lt;span&gt;\&amp;quot;&lt;/span&gt;&lt;span&gt;en-US&lt;/span&gt;&lt;span&gt;\&amp;quot;&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; &amp;lt;&amp;gt; …&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;private: &lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;url: &lt;/span&gt;&lt;span&gt;&amp;quot;https://bookworm.fm/1/&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;%&lt;/span&gt;&lt;span&gt;&amp;amp;lbrace&lt;/span&gt;…&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  …
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first time I tried this, I forgot the trailing slash at the end of the URL and was getting tons of redirects (I'm guessing to the URI with the trailing slash.) 🤷&lt;/p&gt;
&lt;p&gt;Let's do some cleanup on this… we don't care about all the html, just the bit inside the main area.&lt;/p&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-elixir" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;episodes&lt;/span&gt; &lt;span&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;raw_episodes&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Enum&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt; &lt;span&gt;%&lt;/span&gt;&lt;span&gt;&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;request: &lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;episode&lt;/span&gt; &lt;span&gt;-&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;floki_article&lt;/span&gt; &lt;span&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;request&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Floki&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parse_fragment!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Floki&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;quot;article&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;links&lt;/span&gt; &lt;span&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;floki_article&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Floki&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Enum&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt; &lt;span&gt;link&lt;/span&gt; &lt;span&gt;-&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;        &lt;span&gt;link&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;        &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Floki&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;attribute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;quot;href&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;        &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;List&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;first&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;end&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;title&lt;/span&gt; &lt;span&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;floki_article&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Floki&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;quot;h1&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Floki&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;published&lt;/span&gt; &lt;span&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;floki_article&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Floki&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;quot;.entry-time&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Floki&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;attribute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;quot;datetime&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;List&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;first&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;DateTime&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;from_iso8601&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;# &lt;/span&gt;&lt;span&gt;TODO&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; this should be a `with` block&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;elem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;episode&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Map&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;put&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;:floki_article&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;floki_article&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Map&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;put&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;:article&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;Floki&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;floki_article&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;sep: &lt;/span&gt;&lt;span&gt;&amp;quot; &amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Map&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;put&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;:links&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;links&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Map&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;put&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;:title&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;title&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Map&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;put&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;:published&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;published&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;end&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;!-- livebook:{"output":true} --&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-elixir" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;%&lt;/span&gt;&lt;span&gt;&amp;amp;lbrace&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;article: &lt;/span&gt;&lt;span&gt;&amp;quot;1: Getting Things Done by David Allen July 7, 2016  • 1 hour 08 minutes Welcome to Bookworm! To kick things off, we’ve reread a book we both rely on heavily in our lives, Getting Things Done by David Allen. This is the best discussion either of us have had about GTD in a long time. Joe Buhlig Mike Schmitz 33: Analog vs. Digital with Mike Schmitz TPS57: Advanced OmniFocus Setup + Workflow w/ Joe Buhlig Getting Things Done by David Allen Defer Dates in OmniFocus 43 Folders – Merlin Mann SaneBox – Email Management for Any Inbox JIRA Software – Atlassian Confluence – Atlassian The Organized Mind by Daniel J. Levitin Deep Work by Cal Newport Mike's Rating: 4&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt; Joe's Rating: 5 http://traffic.libsyn.com/bookworm/BW001.mp3 Podcast:  Play in new window  |  Download  (Duration: 1:08:39 — 63.0MB) Subscribe:  Apple Podcasts  |  RSS&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;floki_article: &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;&amp;quot;article&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;       &lt;span&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;         &lt;span&gt;&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;&amp;quot;class&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;         …
&lt;/div&gt;&lt;div class="line"&gt;       &lt;span&gt;]&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;links: &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&amp;quot;http://joebuhlig.com&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;&amp;quot;http://mikeschmitz.me/&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;&amp;quot;http://joebuhlig.com/33/&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;     &lt;span&gt;&amp;quot;http://www.asianefficiency.com/podcast/057-joe-buhlig/&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;     &lt;span&gt;&amp;quot;https://www.amazon.com/Getting-Things-Done-Stress-Free-Productivity/dp/0143126563/ref=sr_1_1?tag=bookwormfm-20&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;     &lt;span&gt;&amp;quot;https://discourse.omnigroup.com/t/defer-date-how-to-use/1046&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;&amp;quot;http://www.43folders.com/&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;     &lt;span&gt;&amp;quot;http://www.sanebox.com/&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;&amp;quot;https://www.atlassian.com/software/jira&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;     &lt;span&gt;&amp;quot;https://www.atlassian.com/software/confluence&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;     &lt;span&gt;&amp;quot;https://www.amazon.com/Organized-Mind-Thinking-Straight-Information/dp/0147516315/ref=sr_1_1?tag=bookwormfm-20&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;     &lt;span&gt;&amp;quot;https://www.amazon.com/Deep-Work-Focused-Success-Distracted/dp/1455586692/ref=sr_1_1?tag=bookwormfm-20&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;     &lt;span&gt;&amp;quot;http://traffic.libsyn.com/bookworm/BW001.mp3&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;&amp;quot;http://traffic.libsyn.com/bookworm/BW001.mp3&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;     &lt;span&gt;&amp;quot;http://traffic.libsyn.com/bookworm/BW001.mp3&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;     &lt;span&gt;&amp;quot;https://itunes.apple.com/us/podcast/bookworm/id1132102092?mt=2&amp;amp;ls=1#episodeGuid=http%3A%2F%2Fbookworm.fm%2F%3Fp%3D12&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;     &lt;span&gt;&amp;quot;https://bookworm.fm/feed/podcast/&amp;quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;request: &lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;Req.Response&lt;/span&gt;&lt;span&gt;&amp;amp;lbrace&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;        …
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;%&lt;/span&gt;&lt;span&gt;&amp;amp;lbrace&lt;/span&gt;…&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  …
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Everytime I create one of these tiny blocks, it feels wrong to my DRY-trained programming brain, but I know from experience with Livebooks that it rewards little iterative steps.&lt;/p&gt;
&lt;p&gt;I've thought of a several different things I want to do with this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a table of &amp;quot;most linked to episodes&amp;quot;&lt;/li&gt;
&lt;li&gt;a graph showing all the links&lt;/li&gt;
&lt;li&gt;transformation to markdown notes that could be stuck in someone's (&lt;em&gt;cough&lt;/em&gt; Mike &lt;em&gt;cough&lt;/em&gt;) Obsidian.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let's go!&lt;/p&gt;
&lt;h2&gt;&lt;a class="anchor" href="#️-graphs-and-maps" id="️-graphs-and-maps"&gt;&lt;/a&gt;🗺️ Graphs and Maps&lt;/h2&gt;
&lt;p&gt;I thought that the first thing I'm going to need to do is to transform this list of raw bodies into something that we can actually work with and display as a table.&lt;/p&gt;
&lt;p&gt;I started to add a Smart Cell Data Explorer, which will automatically add dependencies for all it's plugins and things… but I'm running into it freaking out over the EasyHTML/Floki structs… let's go for the throat and try the graph.&lt;/p&gt;
&lt;p&gt;I've done this before for &lt;a href="https://adventofcode.com"&gt;Advent of Code&lt;/a&gt;, so excuse the magic… 🧙&lt;/p&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-elixir" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;graph&lt;/span&gt; &lt;span&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;episodes&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Enum&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;reduce&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Graph&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;fn&lt;/span&gt; &lt;span&gt;%&lt;/span&gt;&lt;span&gt;&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;url: &lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;links: &lt;/span&gt;&lt;span&gt;links&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;g&lt;/span&gt; &lt;span&gt;-&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;links&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Enum&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;reduce&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;g&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;fn&lt;/span&gt; &lt;span&gt;link&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;g&lt;/span&gt; &lt;span&gt;-&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;Graph&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;add_edge&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;g&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;url&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;link&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;end&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;end&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;graph&lt;/span&gt; &lt;span&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;episodes&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Enum&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;reduce&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;graph&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;fn&lt;/span&gt; &lt;span&gt;%&lt;/span&gt;&lt;span&gt;&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;url: &lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;title: &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;g&lt;/span&gt; &lt;span&gt;-&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;Graph&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;label_vertex&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;g&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;url&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;title&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;end&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;!-- livebook:{"output":true} --&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-elixir" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;#Graph&amp;lt;type: directed, num_vertices: 2201, num_edges: 4844&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hey presto, we have a Graph object! We can now run lots of algorithms on the graph, see strongly connected components, find backlinks, all that good stuff.&lt;/p&gt;
&lt;p&gt;But now we need to display it… the naive way with Graphviz!&lt;/p&gt;
&lt;p&gt;LibGraph supplies a &lt;code&gt;Graph.to_dot/1&lt;/code&gt; method that graphviz can read:&lt;/p&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-elixir" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;dot_output&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;elem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Graph&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;to_dot&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;graph&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;IO&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;puts&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;dot_output&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;!-- livebook:{"output":true} --&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-elixir" tabindex="0"&gt;&lt;div class="line"&gt;…
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;1209938012&lt;/span&gt; &lt;span&gt;-&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt; &lt;span&gt;3153079849&lt;/span&gt; &lt;span&gt;[&lt;/span&gt;&lt;span&gt;weight&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;1209938012&lt;/span&gt; &lt;span&gt;-&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt; &lt;span&gt;3305474943&lt;/span&gt; &lt;span&gt;[&lt;/span&gt;&lt;span&gt;weight&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;1209938012&lt;/span&gt; &lt;span&gt;-&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt; &lt;span&gt;3354501824&lt;/span&gt; &lt;span&gt;[&lt;/span&gt;&lt;span&gt;weight&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;1209938012&lt;/span&gt; &lt;span&gt;-&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt; &lt;span&gt;3484679997&lt;/span&gt; &lt;span&gt;[&lt;/span&gt;&lt;span&gt;weight&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    …
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;!-- livebook:{"output":true} --&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-elixir" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;:ok&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ordinarily we could paste that into something like &lt;a href="https://dreampuf.github.io/GraphvizOnline"&gt;Graphviz Online&lt;/a&gt; and we'd be done, but Mike and Joe have linked a &lt;em&gt;lot&lt;/em&gt; of links.&lt;/p&gt;
&lt;p&gt;Let's output a file for safekeeping.&lt;/p&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-elixir" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;File&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;write!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;#&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;__DIR__&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;https://images.evantravers.com/output.dot&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;dot_output&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;!-- livebook:{"output":true} --&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-elixir" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;:ok&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;output.dot&lt;/code&gt; file is understandable by Graphviz tooling:&lt;/p&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-elixir" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;System&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;shell&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;&amp;quot;/opt/homebrew/bin/sfdp -Tpng &lt;/span&gt;&lt;span&gt;#&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;__DIR__&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;https://images.evantravers.com/output.dot &amp;gt; &lt;/span&gt;&lt;span&gt;#&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;__DIR__&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;https://images.evantravers.com/sfdp.png&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;!-- livebook:{"output":true} --&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-elixir" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;0&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="" src="https://images.evantravers.com/articles/2023/08/graph.png" /&gt;&lt;/p&gt;
&lt;p&gt;This whole bit takes a long time… there's a lot of edges.&lt;/p&gt;
&lt;p&gt;I spent some time staring at the initial visualization and realized a few things would need to e straightened up above:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox" /&gt; I need to filter out some really common noisy URLs, like libsyn and iTunes.&lt;/li&gt;
&lt;li&gt;&lt;input checked="" disabled="" type="checkbox" /&gt; I needed to get the episode title or name of the book to label those nodes, maybe set them apart visually.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Normally I'd just keep working &amp;quot;down&amp;quot; the workbook, but to save on network calls it's easier to get the title when I'm doing the &lt;code&gt;Req.get!&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-elixir" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;filtered_graph&lt;/span&gt; &lt;span&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;episodes&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Enum&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt; &lt;span&gt;%&lt;/span&gt;&lt;span&gt;&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;links: &lt;/span&gt;&lt;span&gt;links&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;ep&lt;/span&gt; &lt;span&gt;-&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;Map&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;put&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;ep&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;:links&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;links&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;# one domain&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Enum&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;match?&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;~&lt;/span&gt;r/&lt;span&gt;^&lt;/span&gt;https:&lt;span&gt;\/&lt;/span&gt;&lt;span&gt;\/&lt;/span&gt;bookworm&lt;span&gt;\.&lt;/span&gt;fm/&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;end&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Enum&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;reduce&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Graph&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;fn&lt;/span&gt; &lt;span&gt;%&lt;/span&gt;&lt;span&gt;&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;url: &lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;links: &lt;/span&gt;&lt;span&gt;links&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;title: &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;g&lt;/span&gt; &lt;span&gt;-&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;links&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Enum&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;reduce&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;g&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;fn&lt;/span&gt; &lt;span&gt;link&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;g&lt;/span&gt; &lt;span&gt;-&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;Graph&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;add_edge&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;g&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;url&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;link&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;end&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Graph&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;label_vertex&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;title&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;end&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let's see if that filtering creates a better graphviz image…&lt;/p&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-elixir" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;File&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;write!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;#&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;__DIR__&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;https://images.evantravers.com/output2.dot&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;elem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Graph&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;to_dot&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;filtered_graph&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;System&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;shell&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;&amp;quot;/opt/homebrew/bin/sfdp -Tpng &lt;/span&gt;&lt;span&gt;#&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;__DIR__&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;https://images.evantravers.com/output2.dot &amp;gt; &lt;/span&gt;&lt;span&gt;#&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;__DIR__&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;https://images.evantravers.com/filtered.png&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="" src="https://images.evantravers.com/articles/2023/08/filtered.png" /&gt;&lt;/p&gt;
&lt;p&gt;Better… somewhere I had to cut the code for labeling the bookworm vertices, unsure why that broke. Once again, I'm sure I could pass some better options to graphviz to emulate Obsidian… orrrrr…&lt;/p&gt;
&lt;h2&gt;&lt;a class="anchor" href="#markdown-for-obsidian" id="markdown-for-obsidian"&gt;&lt;/a&gt;Markdown for Obsidian&lt;/h2&gt;
&lt;p&gt;I decided to shift course and try a straightforward and explorable visualization: use &lt;a href="https://obsidian.md"&gt;Obsidian&lt;/a&gt; to visualize the connections.&lt;/p&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-elixir" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;System&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;cmd&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;quot;rm&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;[&lt;/span&gt;&lt;span&gt;&amp;quot;--rf&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;#&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;__DIR__&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;/episodes&amp;quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;System&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;cmd&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;quot;mkdir&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;[&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;#&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;__DIR__&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;/episodes&amp;quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;map&lt;/span&gt; &lt;span&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;episodes&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Enum&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt; &lt;span&gt;%&lt;/span&gt;&lt;span&gt;&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;url: &lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;title: &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt; &lt;span&gt;-&amp;gt;&lt;/span&gt; &lt;span&gt;&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;String&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;&amp;quot;:&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;&amp;quot;-&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt; &lt;span&gt;end&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Map&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;episodes&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Enum&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt; &lt;span&gt;%&lt;/span&gt;&lt;span&gt;&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;title: &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;links: &lt;/span&gt;&lt;span&gt;links&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;article: &lt;/span&gt;&lt;span&gt;article&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt; &lt;span&gt;-&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;safe_title&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;String&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;&amp;quot;:&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;&amp;quot;-&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;content&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;&amp;quot;&amp;quot;&amp;quot;
&lt;/div&gt;&lt;div class="line"&gt;  # &lt;/span&gt;&lt;span&gt;#&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;#&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;article&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;  ## Mentions:
&lt;/div&gt;&lt;div class="line"&gt;  &lt;/span&gt;&lt;span&gt;#&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;Enum&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;reduce&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;links&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;''&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;fn&lt;/span&gt; &lt;span&gt;link&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;str&lt;/span&gt; &lt;span&gt;-&amp;gt;&lt;/span&gt; &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;#&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;- [[&lt;/span&gt;&lt;span&gt;#&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;Map&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;link&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;link&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;]]&amp;quot;&lt;/span&gt; &lt;span&gt;end&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;File&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;write!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;#&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;__DIR__&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;/episodes/&lt;/span&gt;&lt;span&gt;#&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;safe_title&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;.md&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;content&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;end&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;!-- livebook:{"output":true} --&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-elixir" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;:ok&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; …&lt;span&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates a folder called &lt;code&gt;episodes&lt;/code&gt; wherever the &lt;code&gt;.livemd&lt;/code&gt; file lives. Open that folder as a vault in Obsidian, and you'll get something like this:&lt;/p&gt;
&lt;!-- livebook:{"break_markdown":true} --&gt;
&lt;p&gt;&lt;img alt="" src="https://images.evantravers.com/articles/2023/08/obsidian_graph.png" /&gt;&lt;/p&gt;
&lt;!-- livebook:{"break_markdown":true} --&gt;
&lt;p&gt;That's pretty good… but let's set up some data science-y stuff.&lt;/p&gt;
&lt;h2&gt;&lt;a class="anchor" href="#️-data" id="️-data"&gt;&lt;/a&gt;⚗️ Data&lt;/h2&gt;
&lt;p&gt;Let's quickly build a list of &lt;code&gt;Map&lt;/code&gt; objects that Livebook's Explorer Dataframes can understand. We'll use a Graph function on the graph object we built earlier in the workbook to find the number of vertices (links) that have an edge to the current episode… essentially how many &amp;quot;backlinks.&amp;quot;&lt;/p&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-elixir" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;backlinks&lt;/span&gt; &lt;span&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;episodes&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Enum&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt; &lt;span&gt;%&lt;/span&gt;&lt;span&gt;&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;title: &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;url: &lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;published: &lt;/span&gt;&lt;span&gt;published&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt; &lt;span&gt;-&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;%&lt;/span&gt;&lt;span&gt;&amp;amp;lbrace&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;title: &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;published: &lt;/span&gt;&lt;span&gt;DateTime&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;to_iso8601&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;published&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;url: &lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;      &lt;span&gt;backlinks: &lt;/span&gt;&lt;span&gt;Graph&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;in_edges&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;graph&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;url&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Enum&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;count&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;&amp;amp;rbrace&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;end&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;!-- livebook:{"output":true} --&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-elixir" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;%&lt;/span&gt;&lt;span&gt;&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;backlinks: &lt;/span&gt;&lt;span&gt;13&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;title: &lt;/span&gt;&lt;span&gt;&amp;quot;1: Getting Things Done by David Allen&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;url: &lt;/span&gt;&lt;span&gt;&amp;quot;https://bookworm.fm/1/&amp;quot;&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;%&lt;/span&gt;&lt;span&gt;&amp;amp;lbrace&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;backlinks: &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;title: &lt;/span&gt;&lt;span&gt;&amp;quot;2: The Willpower Instinct by Kelly McGonigal&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;    &lt;span&gt;url: &lt;/span&gt;&lt;span&gt;&amp;quot;https://bookworm.fm/2/&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;%&lt;/span&gt;&lt;span&gt;&amp;amp;lbrace&lt;/span&gt;&lt;span&gt;backlinks: &lt;/span&gt;&lt;span&gt;7&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;title: &lt;/span&gt;&lt;span&gt;&amp;quot;3: The War Of Art by Steven Pressfield&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;url: &lt;/span&gt;&lt;span&gt;&amp;quot;https://bookworm.fm/3/&amp;quot;&lt;/span&gt;&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  &lt;span&gt;%&lt;/span&gt;&lt;span&gt;&amp;amp;lbrace&lt;/span&gt;…&lt;span&gt;&amp;amp;rbrace&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;  …
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;!-- livebook:{"attrs":{"assign_to":null,"collect":true,"data_frame":"backlinks","data_frame_alias":"Elixir.Explorer.DataFrame","is_data_frame":false,"lazy":true,"missing_require":"Elixir.Explorer.DataFrame","operations":[{"active":true,"column":null,"data_options":{"backlinks":"integer","title":"string","url":"string"},"datalist":[],"filter":null,"operation_type":"filters","type":"string","value":null}]},"chunks":null,"kind":"Elixir.KinoExplorer.DataTransformCell","livebook_object":"smart_cell"} --&gt;
&lt;pre class="lumis"&gt;&lt;code class="language-elixir" tabindex="0"&gt;&lt;div class="line"&gt;&lt;span&gt;require&lt;/span&gt; &lt;span&gt;Explorer.DataFrame&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;span&gt;backlinks&lt;/span&gt; &lt;span&gt;|&amp;gt;&lt;/span&gt; &lt;span&gt;Explorer.DataFrame&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="" src="https://images.evantravers.com/articles/2023/08/Evaluated.png" /&gt;&lt;/p&gt;
&lt;p&gt;👏👏👏&lt;/p&gt;
&lt;p&gt;And there's the answer: they've mentioned (linked to) &lt;em&gt;How To Read a Book&lt;/em&gt; 41 times.&lt;/p&gt;
&lt;p&gt;Already some interesting insights on there… but there's so much to explore:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Could we see this plotted over a timeline?&lt;/li&gt;
&lt;li&gt;Do references have a half-life, or do some books ring eternal in Mike and Joe's ears?&lt;/li&gt;
&lt;li&gt;How does the episode book score relate to the number of backlinks in the future?&lt;/li&gt;
&lt;li&gt;Could we use a transcription to find book mentions that aren't in shownotes?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That's for another day…&lt;/p&gt;
&lt;p&gt;Check out &lt;a href="https://github.com/evantravers/bookworm-visualization"&gt;the repo&lt;/a&gt; if you are interested.&lt;/p&gt;
&lt;h2&gt;&lt;a class="anchor" href="#references" id="references"&gt;&lt;/a&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://hexdocs.pm/floki/Floki.html"&gt;Floki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hexdocs.pm/libgraph/Graph.html"&gt;Graph&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>trv.rs</author><pubDate>Sat, 19 Aug 2023 12:19:00 GMT</pubDate><guid isPermaLink="true">http://evantravers.com/articles/2023/08/19/visualizing-bookworm/</guid></item><item><title>4 alternative tools to replace Terraform in your Infrastructure as Code</title><link>https://www.patrickdap.com/post/ideas-replace-terraform/</link><description>&lt;p&gt;&lt;em&gt;Terraform is switching to a BSL license, where can we go now?&lt;/em&gt;&lt;/p&gt;&lt;img alt="4 alternative tools to replace Terraform in your Infrastructure as Code" height="150" src="https://www.patrickdap.com/assets/datacenter-code.BZA1EBN3_1ue6y7.jpg" style="float: right; margin: 0 0 12px 18px; border-radius: 8px;" width="150" /&gt;&lt;p&gt;With Hashicorp moving a bit away from their roots in the Open Source world and going into the more locked-in, don't-compete-with-me kinda scenario, it's hard not to start thinking whether you should be looking at jumping ship away from your massive Terraform setup. If you're in that boat, where can you go? What are the alternatives to Terraform? In this article, I'll provide some of my recommendations as to where to go, with some pros and cons on each option. While your mileage might greatly vary depending on your technical requirements and conditions, there won't be a one-size-fits-all solution, so I'll try to cover as many options as possible.&lt;/p&gt;&lt;div style="clear: both;"&gt;&lt;/div&gt;</description><author>Patrick D'appollonio</author><pubDate>Sat, 19 Aug 2023 05:43:54 GMT</pubDate><guid isPermaLink="true">https://www.patrickdap.com/post/ideas-replace-terraform/</guid></item><item><title>Bomb Rush Cyberfunk review</title><link>https://burakku.com/blog/bomb-rush-cyberfunk-review/</link><description>&lt;p&gt;&lt;img alt="Bomb Rush Cyberfunk" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Underwhelming spiritual successor to Jet Set Radio.&lt;/p&gt;
&lt;p&gt;While I may not be the biggest Jet Set Radio fan out there, I still find it to be a pretty fun action/platform game. And when I saw that someone was going to create a Jet Set Radio style game for the modern age, I did get quite excited. However, having dipped my toes into the game now, I feel rather disappointed.&lt;/p&gt;
&lt;p&gt;The movement in Bomb Rush Cyberfunk feels noticeably worse than what Jet Set Radio offered. And I think that's a two-pronged issue, one part being controls and one part being level design. On the control side, I had trouble getting my character to control as I wish. Feels like the characters can cut far too sharp in a direction when coming from a jump, and like it happens in an unpredictable fashion. And the control issues are not helped by the fact that the controls are more intricate than Jet Set Radio's, with many more movement possibilities. I could've taken less if the counterbalance was that it felt more intuitive to move.&lt;/p&gt;
&lt;p&gt;And the early levels feel very sparse in comparison to Jet Set Radio with large open areas without any parts for grinding or jumping. In Jet Set Radio, if you saw a ramp going upwards, you could most likely grind against it. But in Bomb Rush Cyberfunk, it feels like I was constantly trying to grind against level elements that just didn't support it. And having to second-guess what parts of the level work for movement and which don't really kills the flow. The levels really didn't feel like they were designed to allow for stylishly combining various tricks to your heart's contents as long as you had the skills. Maybe I'd learn the movement controls in due time but I'm not sure if the level design would ever allow the same kind of free-flowing movement that Jet Set Radio had.&lt;/p&gt;
&lt;p&gt;The approach to levels seems very different between the two. Jet Set Radio would throw you into a level with a map of all tag locations and big honking arrows pointing to the tags inside the game. Bomb Rush Cyberfunk meanwhile throws you into a map without any tag locations, no arrows and tells you to go spray enough of them to unlock the next objective. Jet Set Radio was more of a time attack whereas Bomb Rush Cyberfunk seems to be more of an exploration. And I don't think exploration really meshes well with the kind of movement-based gameplay associated with Jet Set Radio.&lt;/p&gt;
&lt;p&gt;After I managed to clear the first couple of reputation gates by spraying tags, the gameplay really slowed down as I was now just trying to hunt down any tags I'd missed instead of doing stylish grinds and jumps towards my next objective. The only way to tell apart done and undone tags is with this small shimmering effect on top of the sprays, which is much harder to spot in movement. The minimap would only show you the location of the reputation gate, which would angrily send you back because you hadn't done enough spraying yet. You can't even use the spray button prompt to figure out if you've done a tag, since for some reason you can spray the same tag multiple times. As far as I could tell, there was no benefit in doing so, but it was for some reason still possible.&lt;/p&gt;
&lt;p&gt;The spraying itself also makes little sense. Jet Set Radio showed you exactly what kind of joystick movements you should do, and if you screwed up the sequence of moves, you'd lose valuable spray paint and would need to go collect more. Here, you seem to be able to just wiggle the joystick around for about a second and you're done. No sense of accomplishment for a job well done, no punishment for spraying with your eyes closed. At least I was never able to screw up a single tag by just wiggling my joystick around without paying attention. You might as well just have the spraying happen automatically.&lt;/p&gt;
&lt;p&gt;Bomb Rush Cyberfunk also differs from its spiritual forefather by having a combat system. A combat system that doesn't really feel fun. You might be thrown into a stage with a handful of mob cops and one flying cop, who will fly around and then deliver you heaven's punishment from above. And the game doesn't really feel like giving you any helping hand to figuring out where this flying cop is relative to your position, making it much harder to dodge the incoming attacks. And trying to take down the flying cop is also painful due to lacking any kind of a lock-on system, which seems like something a game throwing many opponents at you at once should have. And even when you are successfully taking down your opponents, it doesn't feel satisfying. More like just some flailing around. If the combat was completely optional, maybe in the style of Mirror's Edge, I might give it a pass, but as far as I can tell, some combat sections are not optional.&lt;/p&gt;
&lt;p&gt;The combat sections also seem to suffer from the same lack of direction as the spraying part of the game. I got thrown into a combat section, and I didn't really know what was my objective. Should I just outrun and escape the cops? Should I beat up every single cop coming my way? Or should I just focus on the flying cop that was in the cutscene? Once again, the only direction that the game seemed to be telling me was "go earn more reputation". Guidance that was not really helpful when I was being hunted by the jetpack police.&lt;/p&gt;
&lt;p&gt;The game does very much pull off the Jet Set Radio style. It does very much look like it was just another Jet Set Radio game released on the Dreamcast, for better and for worse. But while the graphics do look old, it is a very functional design. That was probably to be expected considering how well Jet Set Radio has aged. I might not like the levels from a gameplay perspective, I did enjoy them from an aesthetics perspective. Just maybe if they didn't feel so empty. Soundtrack seemed fine too, although it doesn't rival Jet Set Radio in the sound department. Then again, really no game can. Jet Set Radio's soundtrack was a just succession of bangers, whereas Bomb Rush Cyberfunk has good songs and then it has less good songs.&lt;/p&gt;
&lt;p&gt;I really would've liked to love this game, but I don't think I can as it is. For the 40-euro price tag, it feels pretty underwhelming and undercooked. Some of the issues I had with the game could probably be improved, but I'm not sure if the core gameplay could be improved to a degree that made this feel like worth its price tag. Perhaps once it hits the bargain bin, I might give it another chance. But right now Bomb Rush Cyberfunk is unable to meet my expectations and I opted to refund my copy of the game instead. Not sure if I've ever opted to do that on Steam before. A shame.&lt;/p&gt;</description><author>ブラック</author><pubDate>Sat, 19 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/bomb-rush-cyberfunk-review/</guid></item><item><title>Fintech Engineering Challenges</title><link>https://www.danstroot.com/posts/2023-08-19-fintech-engineering-challenges</link><description>&lt;img alt="post image" src="https://danstroot.imgix.net/assets/blog/img/fintech.jpg" /&gt;&lt;br /&gt;&lt;br /&gt;Having worked in financial services for most of my career I want to share some the software engineering challenges that are associated with tracking and moving money digitally. Most of these lessons have been learned the hard way. Hopefully they will be useful to anyone moving into the fintech space.&lt;br /&gt;&lt;br /&gt;This post &lt;a href="https://www.danstroot.com/posts/2023-08-19-fintech-engineering-challenges"&gt;Fintech Engineering Challenges&lt;/a&gt; first appeared on &lt;a href="https://www.danstroot.com"&gt;Dan Stroot's Blog&lt;/a&gt;</description><author>Dan Stroot</author><pubDate>Sat, 19 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.danstroot.com/posts/2023-08-19-fintech-engineering-challenges</guid></item><item><title>Calipso: amores complicados</title><link>https://jdsalaro.com/poem/calipso-amores-complicados/</link><description>En la mitología griega Calipso (Καλυψώ: «la que oculta») era una ninfa que vivía en la isla de Ogigia, donde, según la Odisea de Homero, detuvo a Odiseo durante siete años 2.</description><author>Jayson Salazar Rodriguez | @jdsalaro | Blog</author><pubDate>Sat, 19 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jdsalaro.com/poem/calipso-amores-complicados/</guid></item><item><title>The Photo Game: Country Roads</title><link>https://andrewbridges.org/the-photo-game-country-roads/</link><description>I love the routine of seeing something, doubling back, and checking it out. It's spontaneous.</description><author>Show Notes</author><pubDate>Fri, 18 Aug 2023 18:53:09 GMT</pubDate><guid isPermaLink="true">https://andrewbridges.org/the-photo-game-country-roads/</guid></item><item><title>Istrian holiday notes and tips</title><link>https://ilearnt.com/blog/istria/</link><description>&lt;p&gt;We recently spent just under two weeks visiting Croatia and in particular the Istrian peninsular. These are my notes and thoughts from the holiday.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Fri, 18 Aug 2023 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/istria/</guid></item><item><title>A Bit of SaaS Weekly: Value Pricing</title><link>https://ethanmick.com/a-bit-of-saas-weekly-value-pricing/</link><description>&lt;p&gt;This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by &lt;a href="https://ethanmick.com/"&gt;Ethan Mick&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;A small note and a humble victory &amp;#x2013; today, I had to pay for the larger plan on &lt;a href="https://ghost.org/?ref=ethanmick.com"&gt;Ghost&lt;/a&gt; because my newsletter has too many members. I totally forgot this&lt;/p&gt;</description><author>Build SaaS with Ethan</author><pubDate>Fri, 18 Aug 2023 13:00:01 GMT</pubDate><guid isPermaLink="true">https://ethanmick.com/a-bit-of-saas-weekly-value-pricing/</guid></item><item><title>Favorite Perfumes</title><link>https://neil.computer/notes/favorite-perfumes/</link><description>&lt;p&gt;A quick condensed list based on over 15 years of exploration in the world of perfumery. I didn&amp;apos;t include obvious classics such as Polo Green and Aramis. If I were to pick 3 perfumes to wear for life, it would be: Hermes Bel Ami, Chanel Antaeus and Serge&lt;/p&gt;</description><author>Neil Panchal</author><pubDate>Fri, 18 Aug 2023 11:14:57 GMT</pubDate><guid isPermaLink="true">https://neil.computer/notes/favorite-perfumes/</guid></item><item><title>Get Into Motion</title><link>https://martinrue.com/get-into-motion/</link><description>Microdose progress and remind yourself what a tiny dose feels like. More often than not, that's all you need to become much more willing to switch gear and go for a bigger hit.</description><author>Martin Rue</author><pubDate>Fri, 18 Aug 2023 05:00:00 GMT</pubDate><guid isPermaLink="true">https://martinrue.com/get-into-motion/</guid></item><item><title>How to use Jupyter Notebooks for Machine Learning and AI tasks</title><link>https://zackproser.com/videos/how-to-use-jupyter-notebooks-for-machine-learning</link><description>In this comprehensive video, I demonstrate how simple it is to get started using Jupyter Notebooks to run arbitrary Python</description><author>Zachary Proser</author><pubDate>Fri, 18 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/videos/how-to-use-jupyter-notebooks-for-machine-learning</guid></item><item><title>Docker Compose - Additional property include is not allowed</title><link>https://studiofreya.org/posts/docker-compose-additional-property-include-is-not-allowed/</link><description>&lt;p&gt;I was about to clean up some docker-compose.yml files, when I found out it is possible to include other files.&lt;/p&gt;
&lt;p&gt;But then I got &lt;code&gt;(root) Additional property include is not allowed&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-bash"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;$ docker-compose pull
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #f92672;"&gt;(&lt;/span&gt;root&lt;span style="color: #f92672;"&gt;)&lt;/span&gt; Additional property include is not allowed
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><author>Studiofreya SSG Site</author><pubDate>Fri, 18 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://studiofreya.org/posts/docker-compose-additional-property-include-is-not-allowed/</guid></item><item><title>Portal with RTX review</title><link>https://burakku.com/blog/portal-with-rtx-review/</link><description>&lt;p&gt;&lt;img alt="Portal with RTX" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;This thing is horrendous.&lt;/p&gt;
&lt;p&gt;I'd heard that Portal with RTX didn't run great, but I thought that when people said that, it meant that their RTX 3060 couldn't run the game. Thankfully, I have an RTX 4090, so I can just brute force through any bad optimisations. What I didn't expect was that the game runs like dogshit on whatever piece of technology you throw at it.&lt;/p&gt;
&lt;p&gt;I launched the game and alt-tabbed before I even loaded in the first stage. The game crashed.&lt;/p&gt;
&lt;p&gt;Relaunched the game and cranked the graphical settings. The game crashed.&lt;/p&gt;
&lt;p&gt;Started the game for the third time, played through enough to get the half-powered portal gun and a bit more, and then alt-tabbed. The game crashed.&lt;/p&gt;
&lt;p&gt;This was all within 10 minutes of playing. I cannot remember if I have ever had a game behave this badly on me before.&lt;/p&gt;
&lt;p&gt;My 1% FPS during the start of the game was also around ten. With a 7800X3D and a 4090. I could run maxed out Cyberpunk 2077 without DLSS smoother than this game. If this kind of hardware cannot run this game without massive stuttering, there is surely no gaming PC on this planet that can run this game smoothly.&lt;/p&gt;
&lt;p&gt;It's also not decisively better graphically, even though that's the only reason for this thing to exist. The environment lighting does look more natural than the 2007 version, but it still very much looks like a PlayStation 3 era game. They did change some assets but not everything, so you are still seeing 2007 quality, just with improved lighting. I guess the improvements are nice enough that were this a working product that I would do my Portal replay in this version. But considering how busted this version is, it's so not worth it to even try.&lt;/p&gt;
&lt;p&gt;Thankfully this is just a free tech demo. Had they charged me even 50 cents for this experience, I would've asked for a refund. The only valid use for this game is to generate screenshots and video clips for Nvidia marketing material. Otherwise it's just an embarrassing and damaging showcase for their RTX Remix.&lt;/p&gt;</description><author>ブラック</author><pubDate>Fri, 18 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/portal-with-rtx-review/</guid></item><item><title>Intro To Effect, Part 4: Concurrency in Effect</title><link>https://ybogomolov.me/04-effect-concurrency</link><description>&lt;p&gt;In this article, we will delve into the world of fibers and their role in functional effect systems like ZIO or Effect. By exploring fibers and their various features, we aim to equip you with the ability to handle concurrency effectively in functional programming environments. We will discuss different concurrency combinators and demonstrate how they simplify the management of concurrent and asynchronous operations.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;Intro to Effect series:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href="https://ybogomolov.me/01-effect-intro"&gt;What is Effect?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://ybogomolov.me/02-effect-handling-errors"&gt;Handling Errors&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://ybogomolov.me/03-effect-managing-dependencies"&gt;Managing Dependencies&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Concurrency in Effect&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://ybogomolov.me/05-effect-stm"&gt;Software Transactional Memory in Effect&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Concurrency plays a vital role in many modern applications, enabling efficient utilisation of resources and improved responsiveness. To manage concurrency effectively, functional programming provides an abstraction called fibers, which simplifies handling concurrent and asynchronous operations. This article will introduce you to fibers and discuss their inherent features, as well as pave the way to explore various concurrency combinators. With fibers as the building blocks, you will gain a better understanding of how to model concurrency in your functional applications with ease. So, let’s dive into the world of fibers and discover their potential in concurrent programming.&lt;/p&gt;

&lt;h1 id="what-is-a-fiber"&gt;What is a Fiber?&lt;/h1&gt;

&lt;p&gt;Effect uses &lt;a href="https://en.wikipedia.org/wiki/Fiber_(computer_science)"&gt;fiber-based concurrency&lt;/a&gt; to execute its tasks.&lt;/p&gt;

&lt;p&gt;Fibers, in the context of functional effect systems like ZIO or Effect, are lightweight, logical threads of execution for concurrent and asynchronous operations. They can be started, paused, interrupted, and resumed. Fibers are building blocks for modelling concurrency in applications and abstract away the complexities of managing threads and synchronization. Fibers allow you to run effects concurrently without having to deal with low-level thread management or race conditions. Instead, libraries like Effect manage the scheduling and resource allocation of fibers for you.&lt;/p&gt;

&lt;p&gt;Here are a few key points about fibers:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;em&gt;Lightweight&lt;/em&gt;: Fibers consume fewer resources than native threads, allowing you to run thousands or even millions of them concurrently without significantly impacting performance. This is because fibers are managed by the effect system’s scheduler and share a pool of native threads.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Composable&lt;/em&gt;: Fibers can be combined to create complex concurrency scenarios. You can easily run multiple fibers in parallel, sequentially, or in various combinations.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Resource-safe&lt;/em&gt;: Fibers make it easier to manage resources such as file handles or database connections in a safe and principled manner. These libraries typically provide resource management utilities to help ensure resources are properly acquired and released.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Error handling&lt;/em&gt;: Fibers allow for functional error handling. If an exception occurs within a fiber, the effect system captures the error and allows the developer to handle it appropriately.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Interruption&lt;/em&gt;: Fibers can be interrupted, which means their execution can be safely stopped if necessary. This is useful for implementing timeouts or cancelling background tasks.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In general, fibers are lightweight, composable concurrency primitives that simplify the management of concurrent and asynchronous operations in functional programs.&lt;/p&gt;

&lt;p&gt;To learn more about how fiber-based interruption model works in Effect, see &lt;a href="https://effect.website/docs/interruption-model"&gt;their awesome documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Fiber-based runtime present in Effect allows evaluating recursive Effect functions without blowing up the stack:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9999&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; 
&lt;span class="c1"&gt;//&amp;gt; RangeError: Maximum call stack size exceeded&lt;/span&gt;

&lt;span class="c1"&gt;// Effect implementation:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addEffect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;addEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runPromise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;addEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9999&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; 
&lt;span class="c1"&gt;//&amp;gt; timestamp=2023-08-17T15:27:55.382Z level=INFO fiber=#0 message=10000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Of course, we could use techniques like trampolining to make our &lt;code class="language-plaintext highlighter-rouge"&gt;add&lt;/code&gt; function work, but fiber-based runtime has more benefits — it is transparent to the user, it provides more predictable performance and is more scalable in general, it allows for easier resource management and composition, and much more.&lt;/p&gt;

&lt;p&gt;Now let’s see how we can leverage this to run our effects concurrently.&lt;/p&gt;

&lt;h1 id="running-effects-concurrently"&gt;Running Effects Concurrently&lt;/h1&gt;

&lt;p&gt;By default, effects are executed &lt;em&gt;sequentially&lt;/em&gt;, even if you use combinators like &lt;code class="language-plaintext highlighter-rouge"&gt;Effect.all&lt;/code&gt;. This may be a surprise, especially if putting an analogy with &lt;code class="language-plaintext highlighter-rouge"&gt;Promise.all&lt;/code&gt; or &lt;code class="language-plaintext highlighter-rouge"&gt;Promise.allSettled&lt;/code&gt;. To run them in a concurrent way, you need to specify the concurrency options explicitly. If you think a bit, this design choice makes perfect sense: Effect can be executed not only in a server environment but also in the browser, where resources may be severely limited. Let’s look at some examples:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, delay: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ms`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; millis`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// :: Effect&amp;lt;never, never, void&amp;gt;[]&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;program&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// :: Effect&amp;lt;never, never, void[]&amp;gt;&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runPromise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;program&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This code will output its results sequentially:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-log"&gt;timestamp=2023-08-18T08:59:41.198Z level=INFO fiber=#0 message="0, delay: 0 ms"
timestamp=2023-08-18T08:59:41.301Z level=INFO fiber=#0 message="1, delay: 100 ms"
timestamp=2023-08-18T08:59:41.503Z level=INFO fiber=#0 message="2, delay: 200 ms"
timestamp=2023-08-18T08:59:41.805Z level=INFO fiber=#0 message="3, delay: 300 ms"
timestamp=2023-08-18T08:59:42.208Z level=INFO fiber=#0 message="4, delay: 400 ms"
timestamp=2023-08-18T08:59:42.712Z level=INFO fiber=#0 message="5, delay: 500 ms"
timestamp=2023-08-18T08:59:43.314Z level=INFO fiber=#0 message="6, delay: 600 ms"
timestamp=2023-08-18T08:59:44.019Z level=INFO fiber=#0 message="7, delay: 700 ms"
timestamp=2023-08-18T08:59:44.821Z level=INFO fiber=#0 message="8, delay: 800 ms"
timestamp=2023-08-18T08:59:45.725Z level=INFO fiber=#0 message="9, delay: 900 ms"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To talk about concurrency, we need to measure how long our effects are executing. Fortunately, Effect comes with batteries included, so profiling our effectful programs is a matter of using the &lt;code class="language-plaintext highlighter-rouge"&gt;Effect.timed&lt;/code&gt; combinator!&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;program&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- transforms result of type `A` into a tuple `[Duration, A]`&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Took &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;formatHrtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toHrTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;))}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;where &lt;code class="language-plaintext highlighter-rouge"&gt;formatHrtime&lt;/code&gt; is a helper to convert &lt;code class="language-plaintext highlighter-rouge"&gt;[number, number]&lt;/code&gt; to a nicely formatted string:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formatHrtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nanoseconds&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;milliseconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nanoseconds&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;microseconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nanoseconds&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;msFormatted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;milliseconds&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;` &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;milliseconds&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;ms`&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;microFormatted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;microseconds&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;` &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;microseconds&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;μs`&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;s&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;msFormatted&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;microFormatted&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now the results of our execution look like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-log"&gt;timestamp=2023-08-18T08:59:41.198Z level=INFO fiber=#0 message="0, delay: 0 ms"
timestamp=2023-08-18T08:59:41.301Z level=INFO fiber=#0 message="1, delay: 100 ms"
timestamp=2023-08-18T08:59:41.503Z level=INFO fiber=#0 message="2, delay: 200 ms"
timestamp=2023-08-18T08:59:41.805Z level=INFO fiber=#0 message="3, delay: 300 ms"
timestamp=2023-08-18T08:59:42.208Z level=INFO fiber=#0 message="4, delay: 400 ms"
timestamp=2023-08-18T08:59:42.712Z level=INFO fiber=#0 message="5, delay: 500 ms"
timestamp=2023-08-18T08:59:43.314Z level=INFO fiber=#0 message="6, delay: 600 ms"
timestamp=2023-08-18T08:59:44.019Z level=INFO fiber=#0 message="7, delay: 700 ms"
timestamp=2023-08-18T08:59:44.821Z level=INFO fiber=#0 message="8, delay: 800 ms"
timestamp=2023-08-18T08:59:45.725Z level=INFO fiber=#0 message="9, delay: 900 ms"
timestamp=2023-08-18T08:59:45.726Z level=INFO fiber=#0 message="Took 4s 529ms 645μs"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To run those tasks concurrently, all we have to do is provide &lt;code class="language-plaintext highlighter-rouge"&gt;options&lt;/code&gt; to the second parameter of &lt;code class="language-plaintext highlighter-rouge"&gt;Effect.all&lt;/code&gt;:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;program&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;concurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="c1"&gt;//                              ^^^^^^^^^^^^^^^^^^ — that's all!&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Took &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;formatHrtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toHrTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;))}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With &lt;code class="language-plaintext highlighter-rouge"&gt;{ concurrency: 5 }&lt;/code&gt; we instructed Effect to run our &lt;code class="language-plaintext highlighter-rouge"&gt;tasks&lt;/code&gt; across five fibers, distributing the load in a round-robin manner:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-log"&gt;timestamp=2023-08-18T09:01:44.362Z level=INFO fiber=#1 message="0, delay: 0 ms"
timestamp=2023-08-18T09:01:44.461Z level=INFO fiber=#2 message="1, delay: 100 ms"
timestamp=2023-08-18T09:01:44.561Z level=INFO fiber=#3 message="2, delay: 200 ms"
timestamp=2023-08-18T09:01:44.661Z level=INFO fiber=#4 message="3, delay: 300 ms"
timestamp=2023-08-18T09:01:44.761Z level=INFO fiber=#5 message="4, delay: 400 ms"
timestamp=2023-08-18T09:01:44.864Z level=INFO fiber=#1 message="5, delay: 500 ms"
timestamp=2023-08-18T09:01:45.064Z level=INFO fiber=#2 message="6, delay: 600 ms"
timestamp=2023-08-18T09:01:45.262Z level=INFO fiber=#3 message="7, delay: 700 ms"
timestamp=2023-08-18T09:01:45.463Z level=INFO fiber=#4 message="8, delay: 800 ms"
timestamp=2023-08-18T09:01:45.664Z level=INFO fiber=#5 message="9, delay: 900 ms"
timestamp=2023-08-18T09:01:45.669Z level=INFO fiber=#0 message="Took 1s 311ms 215μs"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;See how &lt;code class="language-plaintext highlighter-rouge"&gt;fiber#1&lt;/code&gt; took the next task from the queue once it finished the first one? And total execution time is roughly the sum of the two longest tasks from both batches (400 ms and 900 ms), plus some overhead of the Effect runtime and logging.&lt;/p&gt;

&lt;p&gt;We can also specify &lt;code class="language-plaintext highlighter-rouge"&gt;{ concurrency: 'unbounded' }&lt;/code&gt; to make Effect use as many fibers as it wants:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;program&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;concurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unbounded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Took &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;formatHrtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toHrTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;))}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The results will be delivered much quicker — at the cost of using more computational resources:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-log"&gt;timestamp=2023-08-18T09:04:18.453Z level=INFO fiber=#1 message="0, delay: 0 ms"
timestamp=2023-08-18T09:04:18.553Z level=INFO fiber=#2 message="1, delay: 100 ms"
timestamp=2023-08-18T09:04:18.653Z level=INFO fiber=#3 message="2, delay: 200 ms"
timestamp=2023-08-18T09:04:18.753Z level=INFO fiber=#4 message="3, delay: 300 ms"
timestamp=2023-08-18T09:04:18.853Z level=INFO fiber=#5 message="4, delay: 400 ms"
timestamp=2023-08-18T09:04:18.954Z level=INFO fiber=#6 message="5, delay: 500 ms"
timestamp=2023-08-18T09:04:19.054Z level=INFO fiber=#7 message="6, delay: 600 ms"
timestamp=2023-08-18T09:04:19.154Z level=INFO fiber=#8 message="7, delay: 700 ms"
timestamp=2023-08-18T09:04:19.254Z level=INFO fiber=#9 message="8, delay: 800 ms"
timestamp=2023-08-18T09:04:19.354Z level=INFO fiber=#10 message="9, delay: 900 ms"
timestamp=2023-08-18T09:04:19.359Z level=INFO fiber=#0 message="Took 0s 909ms 646μs"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Finally, the most interesting option — &lt;code class="language-plaintext highlighter-rouge"&gt;{ concurrency: 'inherit' }&lt;/code&gt;:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;program&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;concurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inherit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Took &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;formatHrtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toHrTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;))}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When specified just as that, it behaves no differently from &lt;code class="language-plaintext highlighter-rouge"&gt;unbounded&lt;/code&gt; concurrency. But it has a really nice feature: an effect that has &lt;code class="language-plaintext highlighter-rouge"&gt;inherited&lt;/code&gt; concurrency uses the setting of its parent executor! Let me show you an example:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;program&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;concurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inherit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Took &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;formatHrtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toHrTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;))}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parent1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Parent #1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;program&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;withConcurrency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- observe the `Effect.withConcurrency` combinator&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parent2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Parent #2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;program&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;withConcurrency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- and this one will run `program` across only two fibers&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, when we run the &lt;code class="language-plaintext highlighter-rouge"&gt;parent1&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;parent2&lt;/code&gt; effects, we will get different behaviour from our &lt;code class="language-plaintext highlighter-rouge"&gt;program&lt;/code&gt; tasks:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-log"&gt;timestamp=2023-08-18T09:09:25.830Z level=INFO fiber=#0 message="Parent #1"
timestamp=2023-08-18T09:09:25.834Z level=INFO fiber=#1 message="0, delay: 0 ms"
timestamp=2023-08-18T09:09:25.935Z level=INFO fiber=#2 message="1, delay: 100 ms"
timestamp=2023-08-18T09:09:26.034Z level=INFO fiber=#3 message="2, delay: 200 ms"
timestamp=2023-08-18T09:09:26.133Z level=INFO fiber=#4 message="3, delay: 300 ms"
timestamp=2023-08-18T09:09:26.235Z level=INFO fiber=#5 message="4, delay: 400 ms"
timestamp=2023-08-18T09:09:26.336Z level=INFO fiber=#1 message="5, delay: 500 ms"
timestamp=2023-08-18T09:09:26.537Z level=INFO fiber=#2 message="6, delay: 600 ms"
timestamp=2023-08-18T09:09:26.736Z level=INFO fiber=#3 message="7, delay: 700 ms"
timestamp=2023-08-18T09:09:26.941Z level=INFO fiber=#4 message="8, delay: 800 ms"
timestamp=2023-08-18T09:09:27.138Z level=INFO fiber=#5 message="9, delay: 900 ms"
timestamp=2023-08-18T09:09:27.144Z level=INFO fiber=#0 message="Took 1s 312ms 144μs"

timestamp=2023-08-18T09:10:47.273Z level=INFO fiber=#0 message="Parent #2"
timestamp=2023-08-18T09:10:47.277Z level=INFO fiber=#1 message="0, delay: 0 ms"
timestamp=2023-08-18T09:10:47.377Z level=INFO fiber=#2 message="1, delay: 100 ms"
timestamp=2023-08-18T09:10:47.478Z level=INFO fiber=#1 message="2, delay: 200 ms"
timestamp=2023-08-18T09:10:47.679Z level=INFO fiber=#2 message="3, delay: 300 ms"
timestamp=2023-08-18T09:10:47.880Z level=INFO fiber=#1 message="4, delay: 400 ms"
timestamp=2023-08-18T09:10:48.181Z level=INFO fiber=#2 message="5, delay: 500 ms"
timestamp=2023-08-18T09:10:48.483Z level=INFO fiber=#1 message="6, delay: 600 ms"
timestamp=2023-08-18T09:10:48.884Z level=INFO fiber=#2 message="7, delay: 700 ms"
timestamp=2023-08-18T09:10:49.286Z level=INFO fiber=#1 message="8, delay: 800 ms"
timestamp=2023-08-18T09:10:49.791Z level=INFO fiber=#2 message="9, delay: 900 ms"
timestamp=2023-08-18T09:10:49.796Z level=INFO fiber=#0 message="Took 2s 522ms 96μs"
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
  &lt;p&gt;❗️ &lt;strong&gt;Pay attention&lt;/strong&gt;: do not forget to specify the concurrency options to the &lt;code class="language-plaintext highlighter-rouge"&gt;Effect.all&lt;/code&gt; combinator, or else you will be surprised by a not-so-ideal performance of your programs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id="other-concurrency-combinators"&gt;Other Concurrency Combinators&lt;/h2&gt;

&lt;h3 id="zipping"&gt;Zipping&lt;/h3&gt;

&lt;p&gt;We can &lt;code class="language-plaintext highlighter-rouge"&gt;zip&lt;/code&gt; two effects together and specify whether they should happen sequentially or in parallel:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parent1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Parent #1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;program&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;withConcurrency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parent2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Parent #2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;program&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;withConcurrency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;zipped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parent1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parent2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;concurrent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runPromise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;zipped&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But when we run this program, we will see a mess of interleaved logs:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-log"&gt;timestamp=2023-08-18T09:17:26.846Z level=INFO fiber=#1 message="Parent #1"
timestamp=2023-08-18T09:17:26.849Z level=INFO fiber=#2 message="Parent #2"
timestamp=2023-08-18T09:17:26.850Z level=INFO fiber=#3 message="0, delay: 0 ms"
timestamp=2023-08-18T09:17:26.851Z level=INFO fiber=#8 message="0, delay: 0 ms"
timestamp=2023-08-18T09:17:26.951Z level=INFO fiber=#4 message="1, delay: 100 ms"
timestamp=2023-08-18T09:17:26.951Z level=INFO fiber=#9 message="1, delay: 100 ms"
timestamp=2023-08-18T09:17:27.051Z level=INFO fiber=#5 message="2, delay: 200 ms"
timestamp=2023-08-18T09:17:27.051Z level=INFO fiber=#8 message="2, delay: 200 ms"
timestamp=2023-08-18T09:17:27.150Z level=INFO fiber=#6 message="3, delay: 300 ms"
timestamp=2023-08-18T09:17:27.250Z level=INFO fiber=#7 message="4, delay: 400 ms"
timestamp=2023-08-18T09:17:27.252Z level=INFO fiber=#9 message="3, delay: 300 ms"
timestamp=2023-08-18T09:17:27.351Z level=INFO fiber=#3 message="5, delay: 500 ms"
timestamp=2023-08-18T09:17:27.453Z level=INFO fiber=#8 message="4, delay: 400 ms"
timestamp=2023-08-18T09:17:27.553Z level=INFO fiber=#4 message="6, delay: 600 ms"
timestamp=2023-08-18T09:17:27.752Z level=INFO fiber=#5 message="7, delay: 700 ms"
timestamp=2023-08-18T09:17:27.753Z level=INFO fiber=#9 message="5, delay: 500 ms"
timestamp=2023-08-18T09:17:27.952Z level=INFO fiber=#6 message="8, delay: 800 ms"
timestamp=2023-08-18T09:17:28.053Z level=INFO fiber=#8 message="6, delay: 600 ms"
timestamp=2023-08-18T09:17:28.151Z level=INFO fiber=#7 message="9, delay: 900 ms"
timestamp=2023-08-18T09:17:28.156Z level=INFO fiber=#1 message="Took 1s 308ms 98μs"
timestamp=2023-08-18T09:17:28.454Z level=INFO fiber=#9 message="7, delay: 700 ms"
timestamp=2023-08-18T09:17:28.855Z level=INFO fiber=#8 message="8, delay: 800 ms"
timestamp=2023-08-18T09:17:29.356Z level=INFO fiber=#9 message="9, delay: 900 ms"
timestamp=2023-08-18T09:17:29.358Z level=INFO fiber=#2 message="Took 2s 508ms 889μs"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To help differentiate them, we can use a thing called a &lt;em&gt;log span&lt;/em&gt; — basically, a way of providing a label to each log line. Log spans have a lifecycle equal to the executing effect, which is a good thing in our case:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parent1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Parent #1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;program&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;withConcurrency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;withLogSpan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;parent1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parent2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Parent #2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;program&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;withConcurrency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;withLogSpan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;parent2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;zipped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parent1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parent2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;concurrent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runPromise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;zipped&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now we will get a much better output:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-log"&gt;timestamp=2023-08-18T09:23:50.276Z level=INFO fiber=#1 message="Parent #1" parent1=0ms
timestamp=2023-08-18T09:23:50.278Z level=INFO fiber=#2 message="Parent #2" parent2=0ms
timestamp=2023-08-18T09:23:50.280Z level=INFO fiber=#3 message="0, delay: 0 ms" parent1=4ms
timestamp=2023-08-18T09:23:50.280Z level=INFO fiber=#8 message="0, delay: 0 ms" parent2=2ms
timestamp=2023-08-18T09:23:50.380Z level=INFO fiber=#4 message="1, delay: 100 ms" parent1=104ms
timestamp=2023-08-18T09:23:50.381Z level=INFO fiber=#9 message="1, delay: 100 ms" parent2=103ms
timestamp=2023-08-18T09:23:50.481Z level=INFO fiber=#5 message="2, delay: 200 ms" parent1=205ms
timestamp=2023-08-18T09:23:50.481Z level=INFO fiber=#8 message="2, delay: 200 ms" parent2=203ms
timestamp=2023-08-18T09:23:50.581Z level=INFO fiber=#6 message="3, delay: 300 ms" parent1=305ms
timestamp=2023-08-18T09:23:50.680Z level=INFO fiber=#7 message="4, delay: 400 ms" parent1=404ms
timestamp=2023-08-18T09:23:50.680Z level=INFO fiber=#9 message="3, delay: 300 ms" parent2=402ms
timestamp=2023-08-18T09:23:50.782Z level=INFO fiber=#3 message="5, delay: 500 ms" parent1=506ms
timestamp=2023-08-18T09:23:50.884Z level=INFO fiber=#8 message="4, delay: 400 ms" parent2=606ms
timestamp=2023-08-18T09:23:50.982Z level=INFO fiber=#4 message="6, delay: 600 ms" parent1=706ms
timestamp=2023-08-18T09:23:51.182Z level=INFO fiber=#9 message="5, delay: 500 ms" parent2=904ms
timestamp=2023-08-18T09:23:51.183Z level=INFO fiber=#5 message="7, delay: 700 ms" parent1=907ms
timestamp=2023-08-18T09:23:51.383Z level=INFO fiber=#6 message="8, delay: 800 ms" parent1=1107ms
timestamp=2023-08-18T09:23:51.485Z level=INFO fiber=#8 message="6, delay: 600 ms" parent2=1207ms
timestamp=2023-08-18T09:23:51.582Z level=INFO fiber=#7 message="9, delay: 900 ms" parent1=1306ms
timestamp=2023-08-18T09:23:51.586Z level=INFO fiber=#1 message="Took 1s 308ms 593μs" parent1=1310ms
timestamp=2023-08-18T09:23:51.883Z level=INFO fiber=#9 message="7, delay: 700 ms" parent2=1605ms
timestamp=2023-08-18T09:23:52.287Z level=INFO fiber=#8 message="8, delay: 800 ms" parent2=2009ms
timestamp=2023-08-18T09:23:52.785Z level=INFO fiber=#9 message="9, delay: 900 ms" parent2=2507ms
timestamp=2023-08-18T09:23:52.788Z level=INFO fiber=#2 message="Took 2s 509ms 783μs" parent2=2510ms
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="racing"&gt;Racing&lt;/h3&gt;

&lt;p&gt;We can run two (using &lt;code class="language-plaintext highlighter-rouge"&gt;Effect.race&lt;/code&gt; or &lt;code class="language-plaintext highlighter-rouge"&gt;Effect.raceFirst&lt;/code&gt;) or more (&lt;code class="language-plaintext highlighter-rouge"&gt;Effect.raceAll&lt;/code&gt;) effects to compete, with the result being delivered by the quickest, and the rest will be interrupted:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- randomised delay&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, delay: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ms`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; millis`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;winner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;raceAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runPromise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;winner&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Running this program will yield each time different results:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-log"&gt;&amp;gt; ts-node concurrency-parallelism.ts
timestamp=2023-08-18T09:27:43.978Z level=INFO fiber=#1 message="0, delay: 16 ms"

&amp;gt; ts-node concurrency-parallelism.ts
timestamp=2023-08-18T09:27:46.986Z level=INFO fiber=#7 message="6, delay: 2 ms"

&amp;gt; ts-node concurrency-parallelism.ts
timestamp=2023-08-18T09:27:49.442Z level=INFO fiber=#4 message="3, delay: 5 ms"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This could be very helpful if you want to write an effect with a capped execution. In this case, you’ll want to use &lt;code class="language-plaintext highlighter-rouge"&gt;Effect.raceFirst&lt;/code&gt;, as the regular &lt;code class="language-plaintext highlighter-rouge"&gt;race&lt;/code&gt; will yield the result of the first &lt;em&gt;successful&lt;/em&gt; effect, and not just the quickest one:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;succeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; millis`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Result: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;withLogSpan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fetcher&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;failer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;timeout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;500 millis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;catchAll&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;withLogSpan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;failer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;raceFirst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;failer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runPromise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This may yield results like these:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-log"&gt;&amp;gt; ts-node concurrency-parallelism.ts
timestamp=2023-08-18T11:01:08.576Z level=INFO fiber=#1 message="Result: 42" fetcher=382ms

&amp;gt; ts-node concurrency-parallelism.ts
timestamp=2023-08-18T10:59:51.197Z level=ERROR fiber=#2 message="Error: timeout" failer=503ms
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="iterating-an-iterable"&gt;Iterating an Iterable&lt;/h3&gt;

&lt;p&gt;We also can utilise a very useful combinator — &lt;code class="language-plaintext highlighter-rouge"&gt;Effect.forEach&lt;/code&gt; to traverse an array of values, applying an effectful computation to each of them, all with the possibility to execute the traversal concurrently:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Value: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; millis`&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;concurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- or 'inherit', or 'unbounded'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// :: Effect&amp;lt;never, never, void[]&amp;gt;&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runPromise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Running this code yields:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-log"&gt;timestamp=2023-08-18T11:04:23.715Z level=INFO fiber=#3 message="Value: 2"
timestamp=2023-08-18T11:04:24.048Z level=INFO fiber=#2 message="Value: 1"
timestamp=2023-08-18T11:04:24.263Z level=INFO fiber=#3 message="Value: 3"
timestamp=2023-08-18T11:04:24.412Z level=INFO fiber=#2 message="Value: 4"
timestamp=2023-08-18T11:04:24.596Z level=INFO fiber=#1 message="Value: 0"
timestamp=2023-08-18T11:04:24.598Z level=INFO fiber=#3 message="Value: 5"
timestamp=2023-08-18T11:04:24.716Z level=INFO fiber=#1 message="Value: 7"
timestamp=2023-08-18T11:04:25.044Z level=INFO fiber=#1 message="Value: 9"
timestamp=2023-08-18T11:04:25.146Z level=INFO fiber=#2 message="Value: 6"
timestamp=2023-08-18T11:04:25.513Z level=INFO fiber=#3 message="Value: 8"
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="merging"&gt;Merging&lt;/h3&gt;

&lt;p&gt;Another super-cool feature — the ability to execute an array of effectful tasks, merging (reducing) their values together with a &lt;code class="language-plaintext highlighter-rouge"&gt;zero&lt;/code&gt; element:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;succeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Value: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; millis`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;withLogSpan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mapper&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// :: Effect&amp;lt;never, never, number&amp;gt;[]&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sumOfTen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mergeAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- tasks to execute and merge&lt;/span&gt;
  &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- initial accumulator value&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- reducer function&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;concurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// :: Effect&amp;lt;never, never, number&amp;gt;&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runPromise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;sumOfTen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Sum: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;withLogSpan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reducer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will print:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-log"&gt;timestamp=2023-08-18T11:24:26.116Z level=INFO fiber=#1 message="Value: 0" mapper=450ms reducer=451ms
timestamp=2023-08-18T11:24:26.216Z level=INFO fiber=#2 message="Value: 1" mapper=549ms reducer=551ms
timestamp=2023-08-18T11:24:26.320Z level=INFO fiber=#2 message="Value: 4" mapper=104ms reducer=655ms
timestamp=2023-08-18T11:24:26.416Z level=INFO fiber=#3 message="Value: 2" mapper=749ms reducer=751ms
timestamp=2023-08-18T11:24:26.566Z level=INFO fiber=#1 message="Value: 3" mapper=447ms reducer=901ms
timestamp=2023-08-18T11:24:26.583Z level=INFO fiber=#1 message="Value: 7" mapper=16ms reducer=918ms
timestamp=2023-08-18T11:24:26.647Z level=INFO fiber=#3 message="Value: 6" mapper=230ms reducer=982ms
timestamp=2023-08-18T11:24:26.764Z level=INFO fiber=#2 message="Value: 5" mapper=444ms reducer=1099ms
timestamp=2023-08-18T11:24:26.938Z level=INFO fiber=#1 message="Value: 8" mapper=355ms reducer=1273ms
timestamp=2023-08-18T11:24:27.090Z level=INFO fiber=#3 message="Value: 9" mapper=443ms reducer=1425ms
timestamp=2023-08-18T11:24:27.094Z level=INFO fiber=#0 message="Sum: 45" reducer=1429ms
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
  &lt;p&gt;Observe how &lt;code class="language-plaintext highlighter-rouge"&gt;mapper&lt;/code&gt; logs time spent in each effect that was producing the individual values, and &lt;code class="language-plaintext highlighter-rouge"&gt;reducer&lt;/code&gt; accumulates time because it traverses the array of results.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you haven’t guessed already, Effect gives you a built-in map-reduce framework &lt;em&gt;out of the box&lt;/em&gt;! How cool is that?&lt;/p&gt;

&lt;h1 id="conclusion"&gt;Conclusion&lt;/h1&gt;

&lt;p&gt;As you have seen, fibers serve as a powerful and efficient means of handling concurrency in functional programming environments. By abstracting away the complexities of thread management and race conditions, they allow developers to focus on the logic and structure of their applications. With the aid of various concurrency combinators, you can easily manage concurrent execution and resource allocation, resulting in more scalable and robust functional programs.&lt;/p&gt;</description><author/><pubDate>Fri, 18 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ybogomolov.me/04-effect-concurrency</guid></item><item><title>How far can you jump from a swing?</title><link>https://alexmolas.com/2023/08/18/how-far-can-you-jump.html</link><description>In this post I derive the physics of pumping a swing and the physics of jumping from a swing. Then, using both pieces I compute the optimal strategy of jumping from a swing to maximize the traveled distance. I propose this game as a new Olympic sport.</description><author>Alex Molas Blog</author><pubDate>Fri, 18 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://alexmolas.com/2023/08/18/how-far-can-you-jump.html</guid></item><item><title>Sharing Audio Description</title><link>https://brett.coulstock.id.au/sharing-audio-description.html</link><description>My reasons for sharing audio description scripts.</description><author>Brett Coulstock - Writer</author><pubDate>Thu, 17 Aug 2023 12:00:00 GMT</pubDate><guid isPermaLink="true">https://brett.coulstock.id.au/sharing-audio-description.html</guid></item><item><title>250k downloads of Latent Space Pod</title><link>https://www.swyx.io/latent-space-250k</link><description>I last talked about my Latent Space adventures in April and last December. Even as a well regarded developer-part-time-creator, the Latent Space Newsletter + Pod has done much better than I usually do. Here are the stats as of today:</description><author>swyx's site RSS Feed</author><pubDate>Thu, 17 Aug 2023 10:24:44 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/latent-space-250k</guid></item><item><title>What Have I Been Reading This Year?</title><link>https://www.barbariangrunge.com/p/what-have-i-been-reading-this-year</link><description>I started this SubStack approximately a year ago to study writing. Along the way, I read and studied a lot of things. What were they?</description><author>Barbarian Grunge</author><pubDate>Thu, 17 Aug 2023 09:03:02 GMT</pubDate><guid isPermaLink="true">https://www.barbariangrunge.com/p/what-have-i-been-reading-this-year</guid></item><item><title>Why now? So What?</title><link>https://frest.substack.com/p/why-now-so-what</link><description>Sequoia founder Don Valentine famously asked founders: &amp;#8220;Why now?&amp;#8221; and &amp;#8220;So what?&amp;#8221;</description><author>FREST Substack</author><pubDate>Thu, 17 Aug 2023 05:36:23 GMT</pubDate><guid isPermaLink="true">https://frest.substack.com/p/why-now-so-what</guid></item><item><title>Nothing Matters</title><link>https://martinrue.com/nothing-matters/</link><description>It's far too easy to be passive and experience life as the default, but it's clear to me that it works the other way around.</description><author>Martin Rue</author><pubDate>Thu, 17 Aug 2023 05:00:00 GMT</pubDate><guid isPermaLink="true">https://martinrue.com/nothing-matters/</guid></item><item><title>Nenio Gravas</title><link>https://martinrue.com/nenio-gravas/</link><description>Multe tro facilas vivi pasive, kaj sperti la vivon kvazaŭ ĝi estas la defaŭlta stato, sed estas inverse.</description><author>Martin Rue</author><pubDate>Thu, 17 Aug 2023 05:00:00 GMT</pubDate><guid isPermaLink="true">https://martinrue.com/nenio-gravas/</guid></item><item><title>Using the WordStar diamond in 2023</title><link>https://benhoyt.com/writings/wordstar-diamond/</link><description>Describes the WordStar diamond, a wonderful set of key bindings from the 1970s, and how you can use it in 2023 on Linux or Windows.</description><author>Ben Hoyt's technical writing</author><pubDate>Thu, 17 Aug 2023 03:15:00 GMT</pubDate><guid isPermaLink="true">https://benhoyt.com/writings/wordstar-diamond/</guid></item><item><title>State Is the Root of All Evil</title><link>https://backendhance.com/blog/2023/state-is-the-root-of-all-evil/</link><description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;h2 id="was-unterscheidet-eine-anwendung-von-einer-mathematischen-funktion"&gt;
&lt;a class="anchor" href="#was-unterscheidet-eine-anwendung-von-einer-mathematischen-funktion"&gt;Was unterscheidet eine Anwendung von einer (mathematischen) Funktion?&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Eine Funktion nimmt einen Input &lt;code&gt;x&lt;/code&gt; und transformiert ihn zu einem Output. Wenn gilt &lt;code&gt;f(x) = 2 * x&lt;/code&gt;, dann ist das Ergebnis dieser Funktion bei der Eingabe von jedem x immer gleich.&lt;/p&gt;
&lt;p&gt;Es gibt keine Seiteneffekte. Wir nennen dieses Verhalten &lt;em&gt;stateless.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Aber was hat das mit der Frage vom Anfang zutun? Eine Anwendung geht darüber hinaus. (Fast) jede Anwendung muss mit State arbeiten. Sobald wir I/O Operations in unserem System haben, haben wir inhärent State. Und häufig kann ein methodenaufruf, mit dem gleichen Input, zu unterschiedlichen Zeiten, unterschiedliche Resultate liefern. Und das ist auch nicht vermeidbar und liegt in der Natur der Aufgabe, die wir lösen wollen.&lt;/p&gt;</description><author>Backendhance</author><pubDate>Thu, 17 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/state-is-the-root-of-all-evil/</guid></item><item><title>Proposal: A Human Augment Space, v0.1</title><link>https://noahnorman.substack.com/p/the-plan-pt-1</link><description>Long-promised exposition on WTF this thing even is really</description><author>Augmented Realist</author><pubDate>Wed, 16 Aug 2023 17:16:01 GMT</pubDate><guid isPermaLink="true">https://noahnorman.substack.com/p/the-plan-pt-1</guid></item><item><title>RESTful presentation</title><link>https://frest.substack.com/p/restful-presentation</link><description>An elegant tool, for a more civilised age</description><author>FREST Substack</author><pubDate>Wed, 16 Aug 2023 10:31:04 GMT</pubDate><guid isPermaLink="true">https://frest.substack.com/p/restful-presentation</guid></item><item><title>Join my chat</title><link>https://frest.substack.com/p/join-my-chat</link><description>A private space for us to converse and connect</description><author>FREST Substack</author><pubDate>Wed, 16 Aug 2023 09:44:03 GMT</pubDate><guid isPermaLink="true">https://frest.substack.com/p/join-my-chat</guid></item><item><title>Part #2 Live code review: Pinecone Vercel starter template and Retrieval Augmented Generation (RAG).</title><link>https://zackproser.com/videos/typescript-rag-twitch-part2</link><description>Part #2 of our Twitch Livestream: walking through the Pinecone Vercel AI chatbot template commit-by-commit</description><author>Zachary Proser</author><pubDate>Wed, 16 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/videos/typescript-rag-twitch-part2</guid></item><item><title>Cursor Introduction</title><link>https://www.danielcorin.com/til/cursor/intro/</link><description>Cursor Introduction</description><author>Thought Eddies</author><pubDate>Wed, 16 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/cursor/intro/</guid></item><item><title>DEATHLOOP review</title><link>https://burakku.com/blog/deathloop-review/</link><description>&lt;p&gt;&lt;img alt="DEATHLOOP" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;A first-person game about trying to break out of a time loop.&lt;/p&gt;
&lt;p&gt;When DEATHLOOP was first shown to public audiences, I figured that it'd be a shooter game focused around on a time loop. And while it does involve both of those things, it's not really that. It's more of a repeating exploration game, where you go around in circles, collecting clues so that you can solve the temporal puzzle and bust your way out of the titular loop.&lt;/p&gt;
&lt;p&gt;After laying down the premise and teaching you your basic abilities in a linear sequence of gameplay, the game becomes very non-linear by giving you some clues and telling you go to figure out how to solve things. It feels a bit daunting at first, and I waffled around exploring unnecessarily a bit before I understood that I could really just ignore everything but the visionary clues and I'd be golden. Once you understand to just follow those, the game doesn't feel like you're just supposed to explore around and take a stab in the dark. Trying to just blindly explore your way out of it is just gonna bury you in information. I still have absolutely no idea what I was supposed to do with some of the non-plot discoveries I made.&lt;/p&gt;
&lt;p&gt;The story isn't really much anything. There's some exposition about what happened previously to begin the loop, but it's not really important and the game doesn't put much effort into it. Well, that or I just couldn't find the scraps of paper and tape recorders that explain the backstory. The few real revelations of the story also took place right before the end for me, since I guess I just played the non-linear story in that order. The meat of the game's story is really just the clues themselves, and not much anything. Granted, the overall puzzle is an enjoyable mystery but it's not a deep and involved plot. The ending is kinda weird and abrupt too.&lt;/p&gt;
&lt;p&gt;The cast of characters is mainly you and the villains that you need to kill, with one villain being particularly chatty for plot reasons. The less chatty villains are basically all just massive unlikable narcissists for you to hate. This is probably for the best, since if you actually plan on finishing the game, you'll probably have a hard time counting how many times you've killed them. I don't want to claim that they're completely shallow or undeveloped, but quite a lot of them never really registered to me as actual people. More like objectives with legs. And the only non-villain character left over is your player character, who is actually a pretty distinct character and all around fun fella with a penchant for talking to himself.&lt;/p&gt;
&lt;p&gt;For exploration, the main activity of the game, there's four different maps and four different times of day when you can go into them. And while the maps themselves don't really change, the intel you can gather out of them changes with the time of day. I don't think a fresh player can beat this game without coming and going to every single map so much that they should just install a revolving door. And I can't say that I'm really a fan of this. It just kinda feels like lazy level design to make us pour through the same four, relatively small maps with a fine comb. The maps really just aren't that big so you'll definitely get intimate with them in 5–10 hours of gameplay.&lt;/p&gt;
&lt;p&gt;The gunplay in DEATHLOOP doesn't feel amazing. It's fine but not really anything more of that. I think that's because the game actually wants you to stealth through everything. The game does actually suggest to you that you're free to go through the maps as you please, including the possibility of going guns blazing, but the way things blow up in your face if you use firearms suggests that they would really prefer you to sneak around instead. And with the gunplay being what it is, it never really felt like "time for action, baby" as much as it felt like "oh, I fucked up, can I get out of this with these guns and the couple of bullets left in my pocket?" Also felt kinda weird that the magic powers you can get in the game seem very combat-focused when that's really second fiddle to stealth.&lt;/p&gt;
&lt;p&gt;Much like the gunplay, stealth gameplay is "fine". It's not the best nor the worst stealth experience I've had in a game. My chance to go by undetected did get significantly better when I got access to the silenced SMG and I actually had the possibility of deleting enemies without groping their butt. Actually, the silenced SMG is so good that a more pro-stealth player might consider it overpowered, since it allows for insta-kills via precise headshots, and if you fuck up, silent rapid fire onto an enemy until it stops moving. Supposedly there were other silenced weapons to also find, but I didn't really need anything more than the SMG.&lt;/p&gt;
&lt;p&gt;What actually felt a lot more satisfying than sneaking around was taking the high ground with a sniper rifle and taking potshots at the enemies from the distance. Position yourself far away enough and they won't be able to hear the gunshots, leaving them unable to hunt you down and helpless to protect their tender little heads. It also feels more in character as a big burly black ex-military dude to snipe down your enemies than try to sneak your way around them.&lt;/p&gt;
&lt;p&gt;&lt;img alt="I have the high ground." src="boom.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Occasionally the chatty villain also decides to invade the map you're in. The first time this happened, I flew into panic since I had no idea where I was getting shot at and had a hard time fighting her off. Pretty quickly after that I started to figure out her patterns and it became more of an annoyance than a massive threat. I even managed to sneak around her with invisibility, turn off my invisibility for a bit to lure her in, then go invisible again to knife her in the back for some sweet insta-kill revenge. You also need to go do some busywork to unlock your escape route after this invasion, so that really just doubled the annoyance of it all. Not really a big fan of this particular feature.&lt;/p&gt;
&lt;p&gt;There's also a multiplayer aspect where you can play as the invader, but I never touched it. I want to play a single-player campaign that I can put on pause if I needed to go pee. Thankfully opting out of the multiplayer aspect is very much possible, although I believe it did automatically start off in online mode.&lt;/p&gt;
&lt;p&gt;Possibly my favourite part of the game is just the presentation. DEATHLOOP is dripping with this cartoony 1960s vibe and it's fantastic. The sound design is also great. The stealth music is very good at building a tense atmosphere, and the shift in music when you go from stealth to combat, or from combat to stealth, also works well to indicate your status, so it also works from a gameplay perspective. Colt's voice acting is also good, although I'm not so sure about the rest of the voice acting. It at least didn't feel as strong. But the overall presentation of the game is still to its benefit.&lt;/p&gt;
&lt;p&gt;Technically the game seems sound. I get pretty low system utilisation when running max settings at 1440p with a 240 FPS cap, so I assume that this game runs pretty fine on low-end hardware as well. Not a lot of bugs either, although I did manage to softlock several times in the inventory screen, and a couple of times an enemy body ragdolled wildly into space after I've killed them. I also read somewhere that the loading times were tied to your FPS. Can't really confirm that claim, but if that's true, then it sounds kinda bad. Thankfully my loading times were pretty decent with a 7800X3D, an RTX 4090 and a fast SSD. Overall, I don't have a lot of things to gripe about the technical side – a lot of games are put out in far worse state.&lt;/p&gt;
&lt;p&gt;DEATHLOOP is kind of a mixed bag for me. The story didn't feel that special and the gameplay didn't feel that special either. The time loop mechanic, even though it was an interesting concept, didn't really make the game feel that special either. But despite none of the aspects of the game really grabbing me, I still felt compelled to pour about 23 hours into the game to solve the puzzle and reach the ending, so I guess DEATHLOOP was doing something right. I'd give the game a cautious recommendation if you enjoy exploration and stealth, but I wouldn't say that it's a must play title.&lt;/p&gt;</description><author>ブラック</author><pubDate>Wed, 16 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/deathloop-review/</guid></item><item><title>Powerline Connection Speed Monitoring via `pla-util`</title><link>https://peanball.net/2023/08/powerline-monitoring/</link><description>&lt;p&gt;First off, if you can have a real network cable, use that. As temporary measure I set up a powerline connection between
the apartment and basement. Connection speeds vary quite a bit and I wanted to be able to monitor reported network speeds.&lt;/p&gt;</description><author>peanball.net</author><pubDate>Wed, 16 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://peanball.net/2023/08/powerline-monitoring/</guid></item><item><title>Python tip 32: positive lookarounds</title><link>https://learnbyexample.github.io/tips/python-tip-32/</link><description>&lt;p&gt;Lookarounds help to create custom anchors and add conditions within a regex definition. These assertions are also known as &lt;strong&gt;zero-width patterns&lt;/strong&gt; because they add restrictions similar to anchors and are not part of the matched portions. Negative lookarounds were discussed in &lt;a href="https://learnbyexample.github.io/tips/python-tip-29/"&gt;this post&lt;/a&gt;. The syntax for positive lookarounds is shown below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;(?=pat)&lt;/code&gt; positive lookahead assertion&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(?&amp;lt;=pat)&lt;/code&gt; positive lookbehind assertion&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here are some examples:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;s &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'42 apple-5, fig3; x-83, y-20: f12'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# extract digits only if it is followed by ,
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# note that end of string doesn't qualify as this is a positive assertion
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?=&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;,)&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, s)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'5'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'83'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# extract digits only if it is preceded by - and followed by ; or :
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?&amp;lt;=&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;-)&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?=[:;]&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, s)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'20'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# replace 'par' as long as 'part' occurs as a whole word later in the line
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;par(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?=.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*\b&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;part&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'[&lt;/span&gt;&lt;span style="text-decoration: underline; font-style: italic; color: #d2a8a1;"&gt;\g&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;lt;0&amp;gt;]'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'par spare part party'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'[par] s[par]e part party'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With lookbehind assertion (both positive and negative), the pattern used for the assertion cannot &lt;em&gt;imply&lt;/em&gt; matching variable length of text. Fixed length quantifier is allowed. Different length alternations are not allowed, even if the individual alternations are of fixed length.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;s &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'pore42 tar3 dare7 care5'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# not allowed
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?&amp;lt;=&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;tar&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;dare)&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, s)
&lt;/span&gt;&lt;span&gt;re.error: look&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;behind requires fixed&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;width pattern
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# workaround for r'(?&amp;lt;!tar|dare)\d+'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?&amp;lt;!&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;tar)(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?&amp;lt;!&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;dare)&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, s)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'42'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'5'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# workaround for r'(?&amp;lt;=tar|dare)\d+'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(?:(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?&amp;lt;=&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;tar)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?&amp;lt;=&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;dare))&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, s)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'3'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'7'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; The third-party &lt;code&gt;regex&lt;/code&gt; module (&lt;a href="https://pypi.org/project/regex/"&gt;https://pypi.org/project/regex/&lt;/a&gt;) offers advanced features like variable-length lookbehinds, subexpression calls, etc.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/100_page_python_intro"&gt;100 Page Python Intro&lt;/a&gt; and &lt;a href="https://github.com/learnbyexample/py_regular_expressions"&gt;Understanding Python re(gex)?&lt;/a&gt; ebooks.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Wed, 16 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/python-tip-32/</guid></item><item><title>How to use the Artemis MEV framework</title><link>https://mteam.space/posts/how-to-use-the-artemis-mev-framework/</link><description>A comprehensive guide on how to use the Artemis MEV framework to write an MEV bot.</description><author>mteam's Writings</author><pubDate>Wed, 16 Aug 2023 02:57:35 GMT</pubDate><guid isPermaLink="true">https://mteam.space/posts/how-to-use-the-artemis-mev-framework/</guid></item><item><title>CNET didn’t have to delete old articles</title><link>https://seirdy.one/notes/2023/08/15/cnet-didnt-have-to-delete-old-articles/</link><description>&lt;aside&gt;&lt;p&gt;
	Reply to &lt;span class="h-cite u-in-reply-to"&gt;&lt;cite class="p-name"&gt;&lt;a class="u-url" href="https://gizmodo.com/cnet-deletes-thousands-old-articles-google-search-seo-1850721475"&gt;CNET Deletes Thousands of Old Articles to Game Google Search&lt;/a&gt;&lt;/cite&gt; by
		&lt;span class="h-card vcard p-author"&gt;&lt;a class="u-url url" href="https://gizmodo.com/"&gt;&lt;span class="p-name fn n"&gt;Gizmodo&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;
	&lt;/span&gt;
&lt;/p&gt;&lt;/aside&gt;&lt;p&gt;CNET actually didn’t have to delete old articles to improve ranking. If CNET simply removed those articles from its sitemap, used &lt;a href="https://www.w3.org/TR/websub/"&gt;WebSub&lt;/a&gt; to inform Google (and IndexNow to inform Bing, Seznam, and Yandex) of new higher-priority pages, and maybe used &lt;code&gt;robots.txt&lt;/code&gt; to disallow crawling of stale pages: CNET could keep old content but prioritize the crawling of recent content. Nothing I just described is Google-specific; these are all agreed-upon standards that work across several search engines.&lt;/p&gt;&lt;p&gt;I suppose it’s easier to just delete pages, though. Less labor means fewer expenses. After all, this is the outlet that &lt;a href="https://gizmodo.com/cnet-ai-chatgpt-tech-news-1850017739"&gt;cut costs with algorithmically-generated articles&lt;/a&gt;.&lt;/p&gt;</description><author>All content on Seirdy’s Home</author><pubDate>Tue, 15 Aug 2023 19:27:46 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/notes/2023/08/15/cnet-didnt-have-to-delete-old-articles/</guid></item><item><title>How to Unbrick the Xiaomi Dafang Camera</title><link>https://www.hackup.net/2023/08/how-to-unbrick-the-xiaomi-dafang-camera/</link><description>&lt;p&gt;A few years ago, when Xiaomi was making its Dafang WiFi camera, I bought two of them for a relatively low price. Since then, before every family vacation, I&amp;#8217;ve set up one of them to spy on monitor our cat &amp;#8230; &lt;a href="https://www.hackup.net/2023/08/how-to-unbrick-the-xiaomi-dafang-camera/"&gt;Continue reading &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
The post &lt;a href="https://www.hackup.net/2023/08/how-to-unbrick-the-xiaomi-dafang-camera/"&gt;How to Unbrick the Xiaomi Dafang Camera&lt;/a&gt; first appeared on &lt;a href="https://www.hackup.net"&gt;hackup.net&lt;/a&gt;.</description><author>hackup.net</author><pubDate>Tue, 15 Aug 2023 18:26:25 GMT</pubDate><guid isPermaLink="true">https://www.hackup.net/2023/08/how-to-unbrick-the-xiaomi-dafang-camera/</guid></item><item><title>Working Backwards Book Summary</title><link>https://svedic.org/economy/working-backwards-book-summary</link><description>Why read the Working Backwards book? Amazon is not very popular right now, with many media criticizing its treatment of workers and its cut-throat culture. Still, love them or hate them, there are some undeniable achievements. Amazon is a tech &amp;#8230; &lt;a href="https://svedic.org/economy/working-backwards-book-summary"&gt;Continue reading &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;</description><author>Svedic.org</author><pubDate>Tue, 15 Aug 2023 14:39:15 GMT</pubDate><guid isPermaLink="true">https://svedic.org/economy/working-backwards-book-summary</guid></item><item><title>Rolling the Dice: Baldur’s Gate 3 and the Art of Engineering Decisions</title><link>https://www.mattp.tech/the-pittsburgh-roboticist-blog/rolling-the-dice-baldurs-gate-3-and-the-art-of-engineering-decisions</link><description>Diving deep into the world of Baldur’s Gate 3, I've discovered it's more 
than just a game. The choices we make, the strategies we employ, they 
mirror our real-life engineering challenges. From Faerun to the lab, 
there's always more than one path to success. Check out my latest blog post 
to see how this epic game intertwines with the world of engineering!</description><author>The Pittsburgh Roboticist Blog - Matt Puchalski - Pittsburgh Roboticist</author><pubDate>Tue, 15 Aug 2023 13:00:00 GMT</pubDate><guid isPermaLink="true">https://www.mattp.tech/the-pittsburgh-roboticist-blog/rolling-the-dice-baldurs-gate-3-and-the-art-of-engineering-decisions</guid></item><item><title>How Pointers Work and the Weird Math Behind Them</title><link>https://numbersallthewaydown.com/2023/08/14/how-pointers-work-and-the-weird-math-behind-them/</link><description>Pointers are a frequently confusing topic for intermediate computer science students. A misunderstanding about the arithmetic surrounding pointers or even the very nature of what a pointer is, is a significant barrier to students that need to code in C or C++, or do tasks with large data that needs to be efficient. By the end, you'll have a working understanding of pointers and how to modify them with math.</description><author>Numbers All The Way Down</author><pubDate>Tue, 15 Aug 2023 06:18:35 GMT</pubDate><guid isPermaLink="true">https://numbersallthewaydown.com/2023/08/14/how-pointers-work-and-the-weird-math-behind-them/</guid></item><item><title>Website Best Practices</title><link>https://brntn.me/blog/website-best-practices/</link><description>&lt;blockquote&gt;
&lt;p&gt;This post discusses practices promoted by checks in &lt;a href="https://github.com/sesh/ready"&gt;ready&lt;/a&gt;, a tool that I've made to help developers check for security best practices in their websites.
If you're working on a team that's building something served over HTTP you should check it out!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;!-- TOC --&gt;&lt;a name="table-of-contents"&gt;&lt;/a&gt;

&lt;h2&gt;Table of Contents&lt;/h2&gt;
&lt;p&gt;This table of contents also serves a quick guide / checklist.&lt;/p&gt;
&lt;!-- TOC start (generated with https://github.com/derlin/bitdowntoc) --&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="#redirect-http-traffic-to-https"&gt;Redirect HTTP traffic to HTTPS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#cookies-should-be-set-securely"&gt;Cookies should be set securely&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#hsts-headers-must-be-returned"&gt;HSTS headers must be returned&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#you-should-support-ipv6"&gt;You should support IPv6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#return-a-decent-content-security-policy"&gt;Return a decent Content-Security-Policy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#permissions-policy-should-exist-if-the-response-is-html"&gt;Permissions-Policy should exist if the response is HTML&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#dont-allow-your-page-to-be-loaded-in-a-frame"&gt;Don't allow your page to be loaded in a frame&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#referrer-policy-should-be-same-origin-or-no-referrer"&gt;Referrer-Policy should be same-origin or no-referrer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#multiple-nameservers-should-be-configured"&gt;Multiple nameservers should be configured&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#dns-ttl-should-be-longer-than-you-think"&gt;DNS TTL should be longer than you think&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#dont-let-browsers-detect-content-type"&gt;Don't let browsers detect content-type&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#disable-developer-tooling-in-your-production-environment"&gt;Disable developer tooling in your production environment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#maybe-dont-use-x-xss-protection"&gt;Maybe don't use X-XSS-Protection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-first-part-of-your-html-is-critical"&gt;The first part of your HTML is critical&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#set-up-your-favicon-and-your-icon-icon"&gt;Set up your favicon and you icon icon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#dont-use-schemaless-urls-in-your-html"&gt;Don't use schemaless URLs in your HTML&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#configure-subresource-integrity"&gt;Configure subresource integrity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#dont-use-shared-cdns-for-static-assets"&gt;Don't use shared CDNs for static assets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#disable-x-dns-prefetch-control"&gt;Disable X-DNS-Prefetch-Control&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#let-your-feeds-be-consumed-by-browsers"&gt;Let your feeds be consumed by browsers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#compress-responses"&gt;Compress responses&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#expect-ct-is-enabled-by-default-now"&gt;Expect-CT is enabled by default now&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#remove-headers-that-leak-information"&gt;Remove headers that leak information&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#use-a-secure-ssl-configuration"&gt;Use a secure SSL configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#corp-coop-coep-huh"&gt;CORP, COOP, COEP, huh?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#tell-browser-where-to-report-to"&gt;Tell browser where to Report-To&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#dont-set-expires-for-documents"&gt;Don't set Expires for Documents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#do-set-cache-control"&gt;Do set Cache-Control&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#configure-your-email-securely"&gt;Configure your email securely&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#configure-your-email-securely-even-if-you-dont-send-email"&gt;Configure your email securely, even if you don't send email!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#some-well-known-files-you-should-return"&gt;Some well-known files you should return&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#other-guides-and-resources"&gt;Other guides and resources&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- TOC --&gt;&lt;a name="redirect-http-traffic-to-https"&gt;&lt;/a&gt;

&lt;h2&gt;Redirect HTTP traffic to HTTPS&lt;/h2&gt;
&lt;p&gt;You should redirect all HTTP requests to HTTPS using an HTTP 301 redirect.
This should be performed before the request reaches your application using a reverse proxy sitting in front it.
It's safe to include the path requested in the redirect.
When configured correctly this means that the user ends up on the page they are expecting.&lt;/p&gt;
&lt;p&gt;You can use the &lt;a href="https://ssl-config.mozilla.org/"&gt;Mozilla SSL Configuration Generator&lt;/a&gt; to generate a configuration that handles this redirect in a wide variety of server software.&lt;/p&gt;
&lt;p&gt;An alternative to redirecting HTTP to HTTPS is to configure your server to not listen to HTTP connections on port 80.
This is a good solution for APIs, CDNs and other URLs that a user will never enter manually into a browser.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Just a warning: I have seen some scanning software flag endpoints that return a &lt;code&gt;4xx&lt;/code&gt; response for HTTP requests.
Those scanners are simply looking for a redirect on that HTTP connection.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Don't get fancy with other, unexpected, response codes.&lt;/p&gt;
&lt;!-- TOC --&gt;&lt;a name="cookies-should-be-set-securely"&gt;&lt;/a&gt;

&lt;h2&gt;Cookies should be set securely&lt;/h2&gt;
&lt;p&gt;If your page requires cookies you should set them securely and ensure that Javascript on your page cannot access them.
MDN's &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie"&gt;Set-Cookie&lt;/a&gt; page gives a good overview of the ways that server sent cookies can be configured.&lt;/p&gt;
&lt;p&gt;Understand the difference between &lt;code&gt;SameSite=Lax&lt;/code&gt; and &lt;code&gt;SameSite=Strict&lt;/code&gt;.
If you can, set cookies to &lt;code&gt;Strict&lt;/code&gt; but be aware that means cookies will not be sent when requests are initiated by third-party contexts (i.e. someone links a user to your site).
&lt;code&gt;Lax&lt;/code&gt; is the default value in all modern browsers but you should explicitly set this to avoid issues with old browsers.&lt;/p&gt;
&lt;p&gt;Use &lt;code&gt;Secure&lt;/code&gt; in all cookies to ensure that cookies are only available in HTTPS contexts.
Setting this helps to prevent accidental leakage of cookies over HTTP connections that could be vulnerable to person-in-the-middle attacks.&lt;/p&gt;
&lt;p&gt;Use &lt;code&gt;HttpOnly&lt;/code&gt; to prevent cookies from being accessed by Javascript in the client's browser.
There are very few use cases for directly accessing the contents of a server set cookie from Javascript.&lt;/p&gt;
&lt;p&gt;Finally, you can use the &lt;code&gt;Path&lt;/code&gt; attribute to further restrict cookies.
This ensures that the cookie will only be sent to the parts of your application that need it.
For example, if you set &lt;code&gt;Path=/admin&lt;/code&gt; then the cookie will only be sent when requests are made to the &lt;code&gt;/admin&lt;/code&gt; route.&lt;/p&gt;
&lt;p&gt;Remember that cookies can be read and manipulated by the client.
Even with these settings a user or a malicious browser extension/proxy can change the contents of a cookie.
You should never store sensitive information in cookies.&lt;/p&gt;
&lt;p&gt;The best practice is to use a &lt;code&gt;HttpOnly&lt;/code&gt; cookie to store a session id that the server uses to lookup session details from a data store.&lt;/p&gt;
&lt;p&gt;Here's an example &lt;code&gt;Set-Cookie&lt;/code&gt; header from an unauthenticated request to Github:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;Set-Cookie: _octo=GH1.1.827422477.1670273756; Path=/; Domain=github.com; Expires=Tue, 05 Dec 2023 20:55:56 GMT; Secure; SameSite=Lax
&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;You must understand the European Union's &lt;a href="https://gdpr.eu/cookies/"&gt;ePrivacy regulations&lt;/a&gt; and how those relate to your use of cookies.
The regulations require you to gain consent before using any cookies that are not &lt;strong&gt;strictly necessary&lt;/strong&gt;.
Even for necessary cookies you should explain to users of your website why they are required and what actions they perform.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;!-- TOC --&gt;&lt;a name="hsts-headers-must-be-returned"&gt;&lt;/a&gt;

&lt;h2&gt;HSTS headers must be returned&lt;/h2&gt;
&lt;p&gt;The HTTP &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security"&gt;Strict Transport Security&lt;/a&gt; (HSTS) header tells browsers to disallow insecure requests after a successful secure request.
You should return the &lt;code&gt;Strict-Transport-Security&lt;/code&gt; header on all HTTPS requests.
Browsers ignore HSTS headers sent on HTTP requests so you do not need to include this header in your HTTP → HTTPS redirect response.&lt;/p&gt;
&lt;p&gt;Since you should not be supporting HTTP requests it's safe and recommended to set the &lt;code&gt;max-age&lt;/code&gt; of the HSTS header to at least 1 year.&lt;/p&gt;
&lt;p&gt;If you're comfortable that all subdomains will be served using HTTPS you should add &lt;code&gt;includeSubDomains&lt;/code&gt; to the header.
With &lt;a href="https://letsencrypt.org"&gt;Let's Encrypt&lt;/a&gt; and others providing free certificates there is really no excuse to not support HTTPS on all subdomains unless you have specific applications that cannot be configured to use HTTPS.
If this is the case consider moving those applications to another domain and using &lt;code&gt;includeSubDomains&lt;/code&gt; for your primary domain names.&lt;/p&gt;
&lt;p&gt;Here is an example HSTS header with a one year expiry:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;Strict-Transport-Security: max-age=31536000; includeSubdomains
&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the wild you will see a wide range of max-age values, with some major websites going significantly beyond the 1 year recommendation.
Fo example: Twitter, Wikipedia, some Google subdomains and Cash App all set &lt;code&gt;max-age&lt;/code&gt; to two or more years.
For reference, a value of &amp;quot;63072000&amp;quot; represents two years.&lt;/p&gt;
&lt;p&gt;If you are confident that you will &lt;em&gt;never&lt;/em&gt; need to support insecure requests then you can add the &lt;code&gt;preload&lt;/code&gt; directive and follow the instructions on &lt;a href="https://hstspreload.org"&gt;hstspreload.org&lt;/a&gt; to be added to the Chrome preload list.
This is a list of sites that are hardcoded into Chrome (and other browsers) as being HTTPS only.&lt;/p&gt;
&lt;p&gt;If you use the &lt;a href="https://ssl-config.mozilla.org/"&gt;Mozilla SSL Configuration Generator&lt;/a&gt; this header will be included in your configuration.&lt;/p&gt;
&lt;!-- TOC --&gt;&lt;a name="you-should-support-ipv6"&gt;&lt;/a&gt;

&lt;h2&gt;You should support IPv6&lt;/h2&gt;
&lt;p&gt;You should add a &lt;code&gt;AAAA&lt;/code&gt; DNS record that points to the IPv6 address of your web server.
Make sure that you configure your web server to listen on IPv6.&lt;/p&gt;
&lt;p&gt;If an ISP or mobile carrier does not assign an IPv4 address to each individual customer they will often assign public IPv6 addresses.
Connections to websites that only support IPv4 will be sent via a IPv4 in IPv6 tunnel.
Supporting IPv6 will give these users, which includes a large percentage of US cellphones, a more optimal experience.&lt;/p&gt;
&lt;p&gt;A handy tool for testing IPv6 support is &lt;a href="https://screenshots.page/"&gt;screenshots.page&lt;/a&gt;.
It allows you to request screenshots of your website from both an IPv4 and IPv6 connection.
You should expect the images to be identical.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that &lt;code&gt;ready&lt;/code&gt; simply checks for the existence of a &lt;code&gt;AAAA&lt;/code&gt; record.
This doesn't guarantee that your IPv6 version is working correctly.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;!-- TOC --&gt;&lt;a name="return-a-decent-content-security-policy"&gt;&lt;/a&gt;

&lt;h2&gt;Return a decent Content-Security-Policy&lt;/h2&gt;
&lt;p&gt;A Content Security Policy tells modern browsers where it can load resources from and make connections to.
If you are serving a HTML response you must include &lt;code&gt;Content-Security-Policy&lt;/code&gt; in your response headers or a &lt;code&gt;&amp;lt;meta http-equiv&amp;gt;&lt;/code&gt; tag.
You should favour the header unless your page is being served by a hosting provider that does not allow modification of the HTTP headers.&lt;/p&gt;
&lt;p&gt;The policy is made up of directives that, with a few exceptions, allow you to specify server origins for different resource types.
Before writing a policy for your site you should read through MDN's &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP"&gt;Content Security Policy&lt;/a&gt; introduction.&lt;/p&gt;
&lt;p&gt;All Content Security Policies should set the &lt;code&gt;default-src:&lt;/code&gt; directive.
Ideally this will be set to &lt;code&gt;none&lt;/code&gt;, with explicit definitions for all other relevant directives.
To simplify your policy this might be set to &lt;code&gt;self&lt;/code&gt; to allow resources to be loaded from the same domain.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Never allow &lt;code&gt;default-src&lt;/code&gt; to be set to a wildcard value such as &lt;code&gt;https:&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To prevent cross site scripting attacks either &lt;code&gt;default-src&lt;/code&gt; or &lt;code&gt;script-src&lt;/code&gt; must be set.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;script-src&lt;/code&gt; defines where Javascript can be loaded from.
You should set it to a list of URIs that you know will be used to load Javascript files.&lt;/p&gt;
&lt;p&gt;To prevent malicious Javascript connecting to unexpected servers, &lt;code&gt;default-src&lt;/code&gt; or &lt;code&gt;connect-src&lt;/code&gt; should be configured.
If you are not expecting any HTTP requests to be initiated by your Javascript, you should set &lt;code&gt;connect-src&lt;/code&gt; to &lt;code&gt;none&lt;/code&gt;.
You should make sure that your Privacy Policy aligns with the places that Javascript is allowed to send data from the browser.&lt;/p&gt;
&lt;p&gt;You &lt;strong&gt;must not&lt;/strong&gt; include &lt;code&gt;unsafe-inline&lt;/code&gt; in &lt;code&gt;script-src&lt;/code&gt; or &lt;code&gt;default-src&lt;/code&gt;.
This allows inline Javascript to be executed and prevents many of the security mechanisms provided by the policy.
This applies to all sites but is particularly important if you allow user generated content.
Making sure this is not set prevents injection of Javascript by &lt;a href="https://au.pcmag.com/security/95648/facebooks-in-app-browser-injects-javascript-into-third-party-websites"&gt;in-app browsers&lt;/a&gt; and web proxies.&lt;/p&gt;
&lt;p&gt;You should not use &lt;code&gt;unsafe-inline&lt;/code&gt; in &lt;code&gt;style-src&lt;/code&gt; but this has less security implications.&lt;/p&gt;
&lt;p&gt;In addition to the unsafe values, you should avoid values that are effectively wildcards.
This includes &lt;code&gt;https:&lt;/code&gt;, which was intended to force all assets to be loaded over HTTPS.
That's nice in theory but it allows resources to be loaded from all HTTPS servers which is unlikely to be the desired effect.&lt;/p&gt;
&lt;p&gt;Unless you are using legacy HTML embeds, &lt;code&gt;object-src 'none'&lt;/code&gt; should be set.
This disables the use of those legacy object embed mechanisms and closes potential security holes.&lt;/p&gt;
&lt;p&gt;You should set &lt;code&gt;frame-ancestors 'none'&lt;/code&gt; to prevent your page from being loaded in a frame.
This is discussed a little more below.&lt;/p&gt;
&lt;p&gt;During development and testing use the &lt;code&gt;Content-Security-Policy-Report-Only&lt;/code&gt; header to log and report failures without actually blocking the failing directives.
You can roll out policy changes all the way to production using this header.&lt;/p&gt;
&lt;p&gt;For both the &lt;code&gt;Content-Security-Policy&lt;/code&gt; and &lt;code&gt;Content-Security-Policy-Report-Only&lt;/code&gt; headers you should include a &lt;code&gt;report-to&lt;/code&gt; directive that matches a group in your &lt;code&gt;Report-To&lt;/code&gt; header (see below).&lt;/p&gt;
&lt;p&gt;Use the &lt;a href="https://csp-evaluator.withgoogle.com/"&gt;CSP Evaluator&lt;/a&gt; to check your policy and compare it to Github.com's policy to see a real-world example of a complex, but secure, &lt;code&gt;Content-Security-Policy&lt;/code&gt;.
The evaluator gives you advice and feedback about each of the directives in your policy.&lt;/p&gt;
&lt;p&gt;It's also worth checking out is the &lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html"&gt;OWASP Content-Security-Policy Cheatsheet&lt;/a&gt; which takes a more security focussed look at this header.&lt;/p&gt;
&lt;!-- TOC --&gt;&lt;a name="permissions-policy-should-exist-if-the-response-is-html"&gt;&lt;/a&gt;

&lt;h2&gt;Permissions-Policy should exist if the response is HTML&lt;/h2&gt;
&lt;p&gt;It's a frustrating spec but there's no reason not to support &lt;code&gt;Permissions-Policy&lt;/code&gt; in your HTTP response.
The &lt;code&gt;Permissions-Policy&lt;/code&gt; allows you specify an allow list of origins for specific browser features.
This lets you ensure, for example, that the microphone cannot be used by third-party Javascript or an embedded frame without your knowledge.&lt;/p&gt;
&lt;p&gt;The specification includes an &lt;a href="https://github.com/w3c/webappsec-permissions-policy/blob/main/permissions-policy-explainer.md"&gt;explainer document&lt;/a&gt; that gives a great overview.&lt;/p&gt;
&lt;p&gt;A common way to define a &lt;code&gt;Permissions-Policy&lt;/code&gt; is to define a list of features you know you won't use and specify an empty origin.
For example, to disable use of the camera, microphone and autoplaying video you could use:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;microphone=(),camera=(),autoplay=(),geolocation=()
&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;New features are expected to be added in the future.
To ensure backwards compatibility this means a specific feature missing from the &lt;code&gt;Permissions-Policy&lt;/code&gt; is &lt;em&gt;allowing&lt;/em&gt; that feature to be used.
Keep this in mind when you are updating your policy in the future - you need to keep somewhat up to date with new features that browsers are introducing.
A full list of features supported by different browsers is &lt;a href="https://github.com/w3c/webappsec-permissions-policy/blob/main/features.md"&gt;maintained as part of the spec on Github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;Permissions-Policy&lt;/code&gt; will never override client side security prompts for usage of certain features.
Expect that browsers will continue to prompt users before allowing access to the camera, microphone, geolocation and related features.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;An example of a directive added to the &lt;code&gt;Permissions-Policy&lt;/code&gt; by a specific browser is &lt;code&gt;interest-cohort&lt;/code&gt;.
This was proposed by the Chrome team to give sites the ability to open out of &lt;a href="https://developer.chrome.com/docs/privacy-sandbox/floc/"&gt;FLoC&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;!-- TOC --&gt;&lt;a name="dont-allow-your-page-to-be-loaded-in-a-frame"&gt;&lt;/a&gt;

&lt;h2&gt;Don't allow your page to be loaded in a frame&lt;/h2&gt;
&lt;p&gt;Unless you have a specific use case that requires it you should never allow your HTML document to be loaded in a frame.
Loading the page in a frame (or iframe) opens your up to &lt;a href="https://owasp.org/www-community/attacks/Clickjacking"&gt;clickjacking attacks&lt;/a&gt; and can give the parent document permissions on your page that you are not expecting.
It can also be used to hide the true URL from end users.&lt;/p&gt;
&lt;p&gt;There are two different settings that can be used to prevent your page being loaded in a frame:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;frame-ancestors&lt;/code&gt; directive in your &lt;code&gt;Content-Security-Policy&lt;/code&gt; that can be set to &lt;code&gt;none&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;X-Frame-Options&lt;/code&gt; header that should be set to &lt;code&gt;DENY&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both of the above allow you to specify URLs that can load your page in a frame if that's required.
Unless you need to &lt;a href="https://caniuse.com/?search=frame-ancestors"&gt;support &lt;em&gt;very old&lt;/em&gt; browsers&lt;/a&gt; you only need to use the &lt;code&gt;frame-ancestors&lt;/code&gt; directive.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Some security scanning tools will fail you for not including &lt;code&gt;X-Frame-Options&lt;/code&gt; in your response headers.
Provided you have an adequate Content Security Policy set, and you are not supporting old browsers, you should feel comfortable pushing back.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;!-- TOC --&gt;&lt;a name="referrer-policy-should-be-same-origin-or-no-referrer"&gt;&lt;/a&gt;

&lt;h2&gt;Referrer-Policy should be same-origin or no-referrer&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;Referrer-Policy&lt;/code&gt; tells browsers what information it should send when making HTTP requests that originate from your page.&lt;/p&gt;
&lt;p&gt;Setting this to &lt;code&gt;no-referrer&lt;/code&gt; will tell the browser to not set the &lt;code&gt;Referer&lt;/code&gt; header.
&lt;code&gt;same-origin&lt;/code&gt; will set the &lt;code&gt;Referer&lt;/code&gt; header when making requests to the same domain.&lt;/p&gt;
&lt;p&gt;Note that some &lt;a href="https://owasp.org/www-community/attacks/csrf"&gt;Cross Site Request Forgery&lt;/a&gt; (CSRF) tools require the &lt;code&gt;Referer&lt;/code&gt; header to be set on requests.
This is one way that servers can check that the request is coming from the expected page.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that a malicious user can set the &lt;code&gt;Referer&lt;/code&gt; header to any value they like.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If your site does not use &lt;code&gt;POST&lt;/code&gt; forms, use &lt;code&gt;no-referrer&lt;/code&gt;.
Otherwise test whether that's possible and fall back &lt;code&gt;same-origin&lt;/code&gt; if it breaks your CSRF protections.&lt;/p&gt;
&lt;!-- TOC --&gt;&lt;a name="multiple-nameservers-should-be-configured"&gt;&lt;/a&gt;

&lt;h2&gt;Multiple nameservers should be configured&lt;/h2&gt;
&lt;p&gt;When setting up your DNS records you should configure at least two, ideally four, nameservers.
Most DNS providers give you the ability to host your DNS records on different servers using different domain names.&lt;/p&gt;
&lt;p&gt;The more traffic your site has the more nameservers you should have configured.
This distributes load for DNS lookups across more servers.
Effectively it's a form of load balancing.&lt;/p&gt;
&lt;p&gt;For maximum availability, an ideal setup for your nameservers should:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;use different domains to cover for DNS issues on a particular domain (i.e. ns1.linode.com, ns2.linode.com is not ideal)&lt;/li&gt;
&lt;li&gt;use different top level domain extensions to cover for outages with a particular extension (see the suggested Amazon AWS Route 53 setup)&lt;/li&gt;
&lt;li&gt;use servers in different data centres for cover for outages in a particular location (use traceroute to determine if the server is in the same location)&lt;/li&gt;
&lt;li&gt;use servers from different providers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can use &lt;code&gt;dig ns &amp;lt;domain&amp;gt;&lt;/code&gt; to check the nameservers for a domain.
An example response from &lt;a href="https://brntn.me"&gt;brntn.me&lt;/a&gt; looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;;; ANSWER SECTION:
brntn.me.		172800	IN	NS	ns-1391.awsdns-45.org.
brntn.me.		172800	IN	NS	ns-1725.awsdns-23.co.uk.
brntn.me.		172800	IN	NS	ns-386.awsdns-48.com.
brntn.me.		172800	IN	NS	ns-574.awsdns-07.net.
&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The following response for github.com includes a total of 8 name servers from two different providers:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;;; ANSWER SECTION:
github.com.		900	IN	NS	dns1.p08.nsone.net.
github.com.		900	IN	NS	dns2.p08.nsone.net.
github.com.		900	IN	NS	dns3.p08.nsone.net.
github.com.		900	IN	NS	dns4.p08.nsone.net.
github.com.		900	IN	NS	ns-1283.awsdns-32.org.
github.com.		900	IN	NS	ns-1707.awsdns-21.co.uk.
github.com.		900	IN	NS	ns-421.awsdns-52.com.
github.com.		900	IN	NS	ns-520.awsdns-01.net.
&lt;/pre&gt;&lt;/div&gt;&lt;!-- TOC --&gt;&lt;a name="dns-ttl-should-be-longer-than-you-think"&gt;&lt;/a&gt;

&lt;h2&gt;DNS TTL should be longer than you think&lt;/h2&gt;
&lt;p&gt;Higher TTL values for DNS records reduce the frequency of DNS requests that clients have to make.
Removing the DNS lookup from the loading time will improve performance for returning users.
If it's unlikely that you will change your DNS records, consider using a long TTL (around 86400, or 24 hours).&lt;/p&gt;
&lt;p&gt;If you set a long TTL you need to add an additional coordination step to any DNS-related changes.
Start by reducing the TTL significantly, then waiting for the old TTL to expire before making any updates.&lt;/p&gt;
&lt;!-- TOC --&gt;&lt;a name="dont-let-browsers-detect-content-type"&gt;&lt;/a&gt;

&lt;h2&gt;Don't let browsers detect content-type&lt;/h2&gt;
&lt;p&gt;Use the &lt;code&gt;X-Content-Type-Options&lt;/code&gt; header to disable browsers auto-detecting content types by setting it to &lt;code&gt;nosniff&lt;/code&gt;.
This means that your server needs to return a valid &lt;code&gt;Content-Type&lt;/code&gt; for HTML, Images, Javascript and CSS documents.&lt;/p&gt;
&lt;p&gt;Setting this to &lt;code&gt;nostiff&lt;/code&gt; also has a positive affect on the way Cross Origin Read Blocking (CORB) works.
You can read more about that in Google's &lt;a href="https://chromium.googlesource.com/chromium/src/+/master/services/network/cross_origin_read_blocking_explainer.md#determining-whether-a-response-is-corb_protected"&gt;CORB explainer&lt;/a&gt;.&lt;/p&gt;
&lt;!-- TOC --&gt;&lt;a name="disable-developer-tooling-in-your-production-environment"&gt;&lt;/a&gt;

&lt;h2&gt;Disable developer tooling in your production environment&lt;/h2&gt;
&lt;p&gt;It is important to ensure that any developer tooling in your production environment is disabled.
This will help to protect the integrity of your production environment and, in some cases, improve performance.&lt;/p&gt;
&lt;p&gt;Check that debuggers, profilers, source maps, or any other tooling that would be used to inspect or modify the running code are disabled.
Leaving these on can cause performance issues, requests timing out (i.e. with an interactive debug session) and potential security issues.&lt;/p&gt;
&lt;p&gt;Unless you are deliberately enabling them, you should check that API discoverability tools like Swagger and GraphQL Introspection are turned off for your production environments.
Don't give a potential attacker free information about how your APIs work.&lt;/p&gt;
&lt;p&gt;If you are using a framework consult the deployment documentation and make sure that's you're follow all of the production readiness steps.&lt;/p&gt;
&lt;p&gt;You should add automated pre-deployment checks to ensure that your production configuration is correct.
For a Python project that might be a simple search for &lt;code&gt;ipdb&lt;/code&gt;, if you are using Swagger that could be a smoke test that ensures &lt;code&gt;/swagger/index.html&lt;/code&gt; is a 404.&lt;/p&gt;
&lt;!-- TOC --&gt;&lt;a name="maybe-dont-use-x-xss-protection"&gt;&lt;/a&gt;

&lt;h2&gt;Maybe don't use X-XSS-Protection&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;X-XSS-Protection&lt;/code&gt; header can be used to turn on cross-site scripting attack filtering in some older browsers.
Modern browsers, including Chrome and Firefox, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection"&gt;do not use this feature&lt;/a&gt;, effectively enabling the functionality by default.&lt;/p&gt;
&lt;p&gt;Instead you should use a &lt;code&gt;Content-Security-Policy&lt;/code&gt; with a &lt;code&gt;script-src&lt;/code&gt; directive that defines where Javascript can be loaded from.&lt;/p&gt;
&lt;p&gt;If you must support older browsers, include this header with the value &lt;code&gt;1; mode=block&lt;/code&gt; to enable the most strict filtering.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Some scanning software will complain when this header is missing.
As long as you have an appropriate Content Security Policy, and don't support older browsers, you should feel comfortable pushing back.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;!-- TOC --&gt;&lt;a name="the-first-part-of-your-html-is-critical"&gt;&lt;/a&gt;

&lt;h2&gt;The first part of your HTML is critical&lt;/h2&gt;
&lt;p&gt;The first few hundred bytes of HTML is critical to your page rendering time.
Specifically the character encoding &lt;em&gt;must be&lt;/em&gt; &lt;a href="https://html.spec.whatwg.org/multipage/semantics.html#charset"&gt;set in the first 1024 bytes&lt;/a&gt; of your HTML document.&lt;/p&gt;
&lt;p&gt;Your HTML response must start with a valid doctype.
For almost all modern web pages this will be &lt;code&gt;&amp;lt;!doctype html&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Your &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; tag should include a &lt;code&gt;lang&lt;/code&gt; attribute, indicating the language of the page.
This is specified a &lt;code&gt;&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;&lt;/code&gt; for an English language page.&lt;/p&gt;
&lt;p&gt;The first tag in your &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; you should be &lt;code&gt;&amp;lt;meta charset=&amp;quot;utf-8&amp;quot;&amp;gt;&lt;/code&gt; (replacing &lt;code&gt;utf-8&lt;/code&gt; with the appropriate charset).
This ensures that the browser is correctly interpreting your document.&lt;/p&gt;
&lt;p&gt;Explicitly configuring these settings prevents browsers from detecting the values and helps to ensure that pages are rendered as quickly as possible.
You can see the &lt;a href="https://github.com/h5bp/html5-boilerplate/blob/v8.0.0/dist/doc/html.md"&gt;HTML5 Boilerplate documentation&lt;/a&gt; for more details about the way the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; is parsed by modern browsers.&lt;/p&gt;
&lt;p&gt;Use Google's &lt;a href="https://developers.google.com/speed/pagespeed/insights/"&gt;Pagespeed Insights&lt;/a&gt; to test your rendering performance and see tips for improvements.&lt;/p&gt;
&lt;!-- TOC --&gt;&lt;a name="set-up-your-favicon-and-your-icon-icon"&gt;&lt;/a&gt;

&lt;h2&gt;Set up your favicon and your icon icon&lt;/h2&gt;
&lt;p&gt;Follow Audrey Feldroy's excellent &lt;a href="https://github.com/audreyfeldroy/favicon-cheat-sheet"&gt;Favicon Cheat Sheet&lt;/a&gt; and return a ICO file at &lt;code&gt;/favicon.ico&lt;/code&gt;.
In addition you should return a higher resolution PNG or ICO version of the icon in the &lt;code&gt;&amp;lt;link rel=&amp;quot;shortcut icon&amp;quot; href=&amp;quot;&amp;quot;&amp;gt;&lt;/code&gt; tag in your &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://html.spec.whatwg.org/multipage/links.html#rel-icon"&gt;https://html.spec.whatwg.org/multipage/links.html#rel-icon&lt;/a&gt;&lt;/p&gt;
&lt;!-- TOC --&gt;&lt;a name="dont-use-schemaless-urls-in-your-html"&gt;&lt;/a&gt;

&lt;h2&gt;Don't use schemaless URLs in your HTML&lt;/h2&gt;
&lt;p&gt;A schemaless URL starts with &lt;code&gt;//&lt;/code&gt; instead of &lt;code&gt;https://&lt;/code&gt;.
When used in tags like &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; this tells the browser to load the asset with the same scheme as the parent document.
Since we are always loading pages of HTTPS it's best to be explicit and always use &lt;code&gt;https://&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;https://&lt;/code&gt; for resource references will mean that pages loaded over HTTP will have mixed content warnings in all modern browsers.
Prevent that by redirecting HTTP traffic to HTTPS with a 301 status code without returning the actual HTML document.&lt;/p&gt;
&lt;!-- TOC --&gt;&lt;a name="configure-subresource-integrity"&gt;&lt;/a&gt;

&lt;h2&gt;Configure subresource integrity&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity"&gt;Subresource Integrety (SRI)&lt;/a&gt; prevents resources from being manipulated unexpectedly.
A hash of the file is included in the &lt;code&gt;integrity&lt;/code&gt; attribute of &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; tags.
This feature is available in all modern browsers.&lt;/p&gt;
&lt;p&gt;You must use SRI if you are loading assets from a shared CDN where you do not have full control over the file that is being served.
Without SRI, changes to the files being served by the CDN will go undetected, creating a significant security risk.&lt;/p&gt;
&lt;p&gt;Even for files that you are serving yourself SRI adds an extra layer of security, ensuring that static files are not manipulated in-transit or on-disk.
Since this value only needs to be calculated at build time this should have no performance impact.&lt;/p&gt;
&lt;!-- TOC --&gt;&lt;a name="dont-use-shared-cdns-for-static-assets"&gt;&lt;/a&gt;

&lt;h2&gt;Don't use shared CDNs for static assets&lt;/h2&gt;
&lt;!-- Note! This section might not be 100% accurate --&gt;

&lt;p&gt;While the introduction of SRI does help make the use of CDNs for static assets safer, the introduction of &lt;a href="https://developer.chrome.com/blog/http-cache-partitioning/"&gt;HTTP cache partitioning&lt;/a&gt; takes away most of the benefits.&lt;/p&gt;
&lt;p&gt;In the JQuery days the key benefit of using CDNs for static assets was that users might already have those assets in their browser cache.
That meant that big Javascript files that were used on a large percentage of website wouldn't need to be repeatedly downloaded.
Chrome, Firefox and Safari now all use per-origin caches, meaning that each domain will have it's own copy of cached assets.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;As noted above, if you use a CDN for static assets you &lt;strong&gt;must&lt;/strong&gt; use SRI for those resources.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;!-- TOC --&gt;&lt;a name="disable-x-dns-prefetch-control"&gt;&lt;/a&gt;

&lt;h2&gt;Disable X-DNS-Prefetch-Control&lt;/h2&gt;
&lt;p&gt;Setting &lt;code&gt;X-DNS-Prefetch-Control&lt;/code&gt; to &lt;code&gt;off&lt;/code&gt; tells browsers not to make DNS requests for links in a HTML document in advance.&lt;/p&gt;
&lt;p&gt;Browsers have started making these DNS requests early in order to offer performance improvements when the links on a page are clicked.
Unfortunately this can leak information about the contents of the page to the user's DNS provider.&lt;/p&gt;
&lt;p&gt;Privacy advocates do encourage servers to return this header to enhance the privacy of users.
This is especially important if your site includes user controlled content.&lt;/p&gt;
&lt;p&gt;Since the performance improvement is negligible you should include this header and improve the privacy of your users.&lt;/p&gt;
&lt;p&gt;In addition to providing this header, users can configure their browser to disable DNS prefetching if they are concerned about the privacy implications.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that this header is non-standard and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-DNS-Prefetch-Control"&gt;is not recommended by Mozilla&lt;/a&gt; because of inconsistent browser implementations.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;!-- TOC --&gt;&lt;a name="let-your-feeds-be-consumed-by-browsers"&gt;&lt;/a&gt;

&lt;h2&gt;Let your feeds be consumed by browsers&lt;/h2&gt;
&lt;p&gt;RSS and JSON feeds form an integral part of the open web.
You should configure feeds to return the &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt; header to allow them to be read directly from browser-based applications.
The value of the header can safely be &lt;code&gt;*&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;By default this cross origin resource sharing (CORS) setup will only allow GET requests to the feeds.
You can read more about CORS in MDN's &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS"&gt;introductory document&lt;/a&gt;.&lt;/p&gt;
&lt;!-- TOC --&gt;&lt;a name="compress-responses"&gt;&lt;/a&gt;

&lt;h2&gt;Compress responses&lt;/h2&gt;
&lt;p&gt;All modern servers support &lt;code&gt;gzip&lt;/code&gt; and responses should be compressed if the &lt;code&gt;Accept-Encoding&lt;/code&gt; header includes &lt;code&gt;gzip&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;HTML compresses extremely well and compressed responses can significantly improve data transfer times.
For example, the HTML of my Github dashboard is 33.22kB compressed, and 145.78kB uncompressed.
All up the non-image content of the site is 810kB compressed and 3.74MB expanded.&lt;/p&gt;
&lt;p&gt;Most reverse proxies and web servers can be configured to compress the response when the browser indicates that it supports it.
Most servers will cache the compressed versions, but to maximise performance some servers allow you to pre-compress files.
This can be especially beneficial for static assets like Javascript and CSS files.&lt;/p&gt;
&lt;!-- TOC --&gt;&lt;a name="expect-ct-is-enabled-by-default-now"&gt;&lt;/a&gt;

&lt;h2&gt;Expect-CT is enabled by default now&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;Expect-CT&lt;/code&gt; header indicated to browsers that they should check certificate transparency logs for the website's certificate.
This is &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expect-CT"&gt;now the default behaviour&lt;/a&gt; in all browsers that supported the header (Chromium-based browsers).&lt;/p&gt;
&lt;p&gt;You should not include this header in your responses as it is either not supported or not used by browsers previously supporting it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Some security scanning services might complain about this header if it is missing.
Push back. Share the MDN link above explaining that it's the default in all the browsers that supported it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;!-- TOC --&gt;&lt;a name="remove-headers-that-leak-information"&gt;&lt;/a&gt;

&lt;h2&gt;Remove headers that leak information&lt;/h2&gt;
&lt;p&gt;Some frameworks, CDNs, servers and gateways include additional headers in the response that can leak information.
Generally, you want to avoid including any non-required information in your headers.
Any additional information will give an attacker more details about your infrastructure and tooling.&lt;/p&gt;
&lt;p&gt;It's especially important to remove any references to specific version numbers.
Version numbers can be directly linked to known vulnerabilities, making an attacker's job much easier.&lt;/p&gt;
&lt;p&gt;Audit your HTTP response headers by making requests to your site with a tool like Postman or &lt;code&gt;curl&lt;/code&gt;.
Look out for headers like: &lt;code&gt;Server&lt;/code&gt;, &lt;code&gt;X-Server&lt;/code&gt;, &lt;code&gt;Via&lt;/code&gt;, &lt;code&gt;X-Powered-By&lt;/code&gt;, &lt;code&gt;X-AspNet-Version&lt;/code&gt; and &lt;code&gt;Served-By&lt;/code&gt;.&lt;/p&gt;
&lt;!-- TOC --&gt;&lt;a name="use-a-secure-ssl-configuration"&gt;&lt;/a&gt;

&lt;h2&gt;Use a secure SSL configuration&lt;/h2&gt;
&lt;p&gt;You should only support &lt;code&gt;TLSv1.2&lt;/code&gt; and &lt;code&gt;TLSv1.1&lt;/code&gt; for connections to your server.
Older SSL/TLS protocols have known vulnerabilities that make them inappropriate for modern web applications.
The only relevant browsers that do not support this configuration are Internet Explorer 8-10, which you would likely prefer not to support anyway.&lt;/p&gt;
&lt;p&gt;Use secure 2048 or 3072 bit RSA keys for your SSL certificates.
If you use &lt;code&gt;certbot&lt;/code&gt; or similar this will be handled for you.&lt;/p&gt;
&lt;p&gt;DNS CAA should be configured to ensure that only Certificate Authorities you expect can issue certificates for your domain.
The policy should disallow wildcard certificates.
This website uses DNS CAA to allow both ZeroSSL and Let's Encrypt to issue certificates using this configuration:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;issue: letsencrypt.org flags:0
issue: sectigo.com flags:0
issuewild: ; flags:0
&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Additionally, those CAA records can include &lt;code&gt;accounturi&lt;/code&gt; and &lt;code&gt;validationmethods&lt;/code&gt; directives to further restrict how ACME clients can issue certificates.&lt;/p&gt;
&lt;p&gt;Use the &lt;a href="https://ssl-config.mozilla.org/"&gt;Mozilla SSL Configuration Generator&lt;/a&gt; to create your server configurations and the Qualys &lt;a href="https://www.ssllabs.com/ssltest/"&gt;SSL Server Test&lt;/a&gt; to test your configuration.
You should be aiming for an A or A+ on the SSL Server Test.&lt;/p&gt;
&lt;p&gt;See SSL Lab's &lt;a href="https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices"&gt;SSL and TLS Deployment Best Practices&lt;/a&gt; guide for a complete list of practices surrounding HTTPS and certificates.&lt;/p&gt;
&lt;!-- TOC --&gt;&lt;a name="corp-coop-coep-huh"&gt;&lt;/a&gt;

&lt;h2&gt;CORP, COOP, COEP, huh?&lt;/h2&gt;
&lt;p&gt;Cross Origin Resource Policy (CORP), Cross Origin Opener Policy (COOP) and Cross Origin Embedder Policy (COEP) are part of a set of new HTTP security headers introduced in 2020 / 2021.
These headers are supported by all modern browsers and help to prevent both side-channel attacks and cross site scripting attacks.&lt;/p&gt;
&lt;p&gt;Scott Helme has the definitive guide on how to use these headers:
&lt;a href="https://scotthelme.co.uk/coop-and-coep/"&gt;https://scotthelme.co.uk/coop-and-coep/&lt;/a&gt;&lt;/p&gt;
&lt;!-- TOC --&gt;&lt;a name="tell-browser-where-to-report-to"&gt;&lt;/a&gt;

&lt;h2&gt;Tell browser where to Report-To&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;Report-To&lt;/code&gt; header indicates to browsers how they should report issues to.
This is a relatively new header but is supported by all modern browsers for reporting Content Security Policy issues.&lt;/p&gt;
&lt;p&gt;Here is an example &lt;code&gt;Report-To&lt;/code&gt; header from brntn.me:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;Report-To: {&amp;quot;group&amp;quot;:&amp;quot;default&amp;quot;,&amp;quot;max_age&amp;quot;:31536000,&amp;quot;endpoints&amp;quot;:[{&amp;quot;url&amp;quot;:&amp;quot;https://brntn.report-uri.com/a/d/g&amp;quot;}],&amp;quot;include_subdomains&amp;quot;:true}
&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here the endpoint is powered by report-uri.com, but it could be a path on your domain configured to handle these reports.
The &lt;code&gt;group&lt;/code&gt; is the name of the reporting endpoint that can be referenced in other headers.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When using &lt;code&gt;Report-To&lt;/code&gt; you should make sure that your Content Security Policy &lt;strong&gt;does not&lt;/strong&gt; include the &lt;code&gt;report-sample&lt;/code&gt; directive. This directive tells browsers to share the script that violates the policy to your reporting provider. Sending user scripts to your server invites potential privacy issues, especially where those scripts are being used for accessibility purposes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;!-- TOC --&gt;&lt;a name="dont-set-expires-for-documents"&gt;&lt;/a&gt;

&lt;h2&gt;Don't set Expires for Documents&lt;/h2&gt;
&lt;p&gt;For dynamic content you should prefer setting the &lt;code&gt;Cache-Control&lt;/code&gt; header over an &lt;code&gt;Expires&lt;/code&gt; header.
Expires allows you to specific a time that a document should no longer be considered valid.
Setting Expires to an invalid date (i.e. &amp;quot;0&amp;quot;) will tell a browser or proxy to not cache the document.&lt;/p&gt;
&lt;p&gt;The problem with the Expires header is that it relies on the server and client times being accurate.
This isn't always going to be reliable and can result in some unexpected behaviour.&lt;/p&gt;
&lt;p&gt;A use case where Expires does make sense is for static assets with expiry dates set in the distant future.&lt;/p&gt;
&lt;!-- TOC --&gt;&lt;a name="do-set-cache-control"&gt;&lt;/a&gt;

&lt;h2&gt;Do set Cache-Control&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;Cache-Control&lt;/code&gt; header gives you more control over how and where a document is cached.
Most documents should be able set &lt;code&gt;max-age&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;HTML documents should use a low &lt;code&gt;max-age&lt;/code&gt; value (less than 24 hours).
Static assets should support cache-busting and use a large &lt;code&gt;max-age&lt;/code&gt; (over 30 days).&lt;/p&gt;
&lt;p&gt;User the &lt;code&gt;private&lt;/code&gt; directive to indicate to caches that the response is private and should only be cached in ways compatible with that.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.mnot.net/cache_docs/"&gt;Mark Nottingham&lt;/a&gt; provides an in depth guide to caching which all web developers should read.&lt;/p&gt;
&lt;!-- TOC --&gt;&lt;a name="configure-your-email-securely"&gt;&lt;/a&gt;

&lt;h2&gt;Configure your email securely&lt;/h2&gt;
&lt;p&gt;If you send email from your domain you need to ensure that you have SPF, DMARC and DKIM set up.
Email administrators should look into MTA-STS and TLS-RPT to ensure that your email is sent via TLS.&lt;/p&gt;
&lt;p&gt;A Sender Policy Framework (SPF) DNS record tells mail servers what servers are allowed to send mail for your domain.
This is checked by servers receiving email from your domain.
SPF records were previously implemented as an &lt;code&gt;SPF&lt;/code&gt; DNS record, this has been replaced with a simple &lt;code&gt;TXT&lt;/code&gt; record.
Your SPF record should include &lt;code&gt;-all&lt;/code&gt; to explicitly deny email that is sent from a server that isn't in the allow list.&lt;/p&gt;
&lt;p&gt;You should configure your SPF record based on the recommendations of your email provider(s).&lt;/p&gt;
&lt;p&gt;You should &lt;strong&gt;not&lt;/strong&gt; return an &lt;code&gt;SPF&lt;/code&gt; DNS record, instead favouring the &lt;code&gt;TXT&lt;/code&gt; record.&lt;/p&gt;
&lt;p&gt;Domain Keys Identified Mail (DKIM) allows a domain owner to cryptographically sign parts of a message to ensure that it hasn't been tampered with.
Each sending server for your domain will have it's own DKIM DNS record configured.
Those records contain a public key which corresponds with the private key used by that sender to sign mail.
Like the SPF records, you should configure DKIM based on the recommendations of your email provider(s).
A reference to the key is included in the email headers.&lt;/p&gt;
&lt;p&gt;The Domain-based Message Authentication, Reporting and Conformance (DMARC) DNS record tells servers what to do with mail that doesn't meet the SPF or DKIM policy.
You should use &lt;code&gt;p=reject&lt;/code&gt; to block mail that fails, and you should include &lt;code&gt;rua=mailto:&amp;lt;email&amp;gt;&lt;/code&gt; to ensure that you receive aggregated notifications of failures.
The DMARC record is served as a TXT DNS record on the &amp;quot;_dmarc.&amp;quot; subdomain.
For example, email sent from &amp;quot;brntn.me&amp;quot; will use the &amp;quot;_dmarc.brntn.me&amp;quot; record.&lt;/p&gt;
&lt;p&gt;Two guides that are useful to refer to when implementing MX records:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.gov.uk/guidance/set-up-government-email-services-securely"&gt;Gov.UK: Set Up Government Email Services Securely&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fastmail.blog/advanced/spf-dkim-dmarc/"&gt;Fastmail guide to SPF, DKIM and DMARC&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- TOC --&gt;&lt;a name="configure-your-email-securely-even-if-you-dont-send-email"&gt;&lt;/a&gt;

&lt;h2&gt;Configure your email securely, even if you don't send email!&lt;/h2&gt;
&lt;p&gt;If you don't use your domain for sending email you should make sure that mail servers know that.
&lt;a href="https://www.gov.uk/guidance/protect-domains-that-dont-send-email"&gt;Gov.uk&lt;/a&gt; has a detailed guide about how to implement this to help block spoofed emails.&lt;/p&gt;
&lt;p&gt;Use a &amp;quot;null MX record&amp;quot; and a deny-all SPF record to prevent email being sent from your domain by others.&lt;/p&gt;
&lt;p&gt;The null MX record has a priority of &amp;quot;0&amp;quot; and a value of &amp;quot;.&amp;quot;.
Most good DNS providers will let you configure this.&lt;/p&gt;
&lt;p&gt;An SPF record should be created with the value &lt;code&gt;v=spf1 -all&lt;/code&gt;.
This tells mail servers that you do not have any sending servers and to deny all email coming from this domain.&lt;/p&gt;
&lt;p&gt;If you want notifications for attempts to send email from your domains you can configure a DMARC record with &lt;code&gt;p=reject&lt;/code&gt;.&lt;/p&gt;
&lt;!-- TOC --&gt;&lt;a name="some-well-known-files-you-should-return"&gt;&lt;/a&gt;

&lt;h2&gt;Some well-known files you should return&lt;/h2&gt;
&lt;p&gt;There's two simple text files that all non-trivial websites should return.&lt;/p&gt;
&lt;p&gt;The classic is &lt;code&gt;/robots.txt&lt;/code&gt; for telling bots what they can and can't access.
There's no rule that states that bots must follow your guidelines here but most well-behaved bots do.
Google has a &lt;a href="https://developers.google.com/search/docs/crawling-indexing/robots/intro"&gt;good guide&lt;/a&gt; on how to create a robots.txt.&lt;/p&gt;
&lt;p&gt;Two quick things to note on your robots.txt:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It's supposed to be accessed by bots. Make sure your firewall isn't blocking access by those user agents.&lt;/li&gt;
&lt;li&gt;It can give an attacker information about your site. Avoid using exact paths for things like admin interfaces, instead use wildcards or use Google's advice about using a &lt;code&gt;noindex&lt;/code&gt; meta tag on those pages instead.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You should include details on how to contact your security team in a file at &lt;code&gt;/.well-known/security.txt&lt;/code&gt;.
The &lt;a href="https://securitytxt.org/"&gt;proposed standard&lt;/a&gt; for this file is available.
At a minimum a contact email address and expiry date should be included.&lt;/p&gt;
&lt;!-- TOC --&gt;&lt;a name="other-guides-and-resources"&gt;&lt;/a&gt;

&lt;h2&gt;Other guides and resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://infosec.mozilla.org/guidelines/web_security"&gt;https://infosec.mozilla.org/guidelines/web_security&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://seirdy.one/posts/2020/11/23/website-best-practices/"&gt;https://seirdy.one/posts/2020/11/23/website-best-practices/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- TOC --&gt;&lt;a name="post-history"&gt;&lt;/a&gt;

&lt;h2&gt;Post History&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;2023-08-15: Published initial version that has been in progress for 6+ months.&lt;/li&gt;
&lt;li&gt;2023-08-25: Updates to caching, csp, DNS CAA and Report-To sections based on feedback provided in &lt;a href="https://github.com/sesh/ready/issues?q=author%3Aseirdy+created%3A%3C%3D2023-08-24"&gt;Github Issues by Seidy&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;</description><author>Brenton Cleeland</author><pubDate>Tue, 15 Aug 2023 04:00:00 GMT</pubDate><guid isPermaLink="true">https://brntn.me/blog/website-best-practices/</guid></item><item><title>We moved our Cloud operations to a Kubernetes Operator</title><link>https://sklar.rocks/questdb-cloud-operator/</link><description>&lt;p&gt;Kubernetes operators seem to be everywhere these days. There are hundreds of them available to install in your cluster. Operators manage everything from X.509 certificates to enterprise-y database deployments, service meshes, and monitoring stacks. But why add even more complexity to a system that many say is already convoluted? With an operator controlling cluster resources, if you have a bug, you now have two problems: figuring out which of the myriad of Kubernetes primitives is causing the issue, as well as understanding how that dang operator is contributing to the problem!&lt;/p&gt;
&lt;p&gt;So when I proposed the idea of introducing a custom-built operator into our Cloud infrastructure stack, I knew that I had to really sell it as a true benefit to the company, so people wouldn't think that I was spending my time on a piece of RDD vaporware. After the go-ahead to begin the project, I spent my days learning about Kubernetes internals, scouring the internet far and wide to find content about operator development, and writing (and rewriting) the base of what was to become our QuestDB Cloud operator.&lt;/p&gt;
&lt;p&gt;After many months of hard work, we've finally deployed this Kubernetes operator into our production Cloud. Building on top of our existing 'one-shot-with-rollback' provisioning model, the operator adds new functionality that allows us to perform complex operations like automatically recovering from node failure, and will also orchestrate upcoming QuestDB Enterprise features such as High Availability and Cold Storage.&lt;/p&gt;
&lt;p&gt;If we've done my job correctly, you, as a current or potential end user will continue to enjoy the stability and easy management of your growing QuestDB deployments, without noticing a single difference. This is the story of how we accomplished this engineering feat and why we even did it in the first place.&lt;/p&gt;
&lt;h2 id="original-system-architecture"&gt;Original System Architecture&lt;/h2&gt;
&lt;p&gt;To manage customer instances in our cloud, we use a queue-based system that is able to provision databases across different AWS accounts and regions using a centralized control plane known simply as "the provisioner."&lt;/p&gt;
&lt;p&gt;&lt;img alt="Provisioner-Based infrastructure" src="/img/blog/questdb-cloud-operator/provisioner-infrastructure.webp" /&gt;&lt;/p&gt;
&lt;p&gt;When a user creates, modifies, or deletes a database in the QuestDB Cloud, an application backend server asynchronously sends a message that describes the change to a backend worker, which then appends that message to a Kafka log. Each message contains information about the target database (its unique identifier, resident cluster, and information about its config, runtime, and storage) as well as the specific set of operations that are to be performed.&lt;/p&gt;
&lt;p&gt;These instructional messages get picked up by one of many provisioner workers, each of which subscribes to a particular Kafka log partition. Once a provisioner worker receives a message, it is decoded and its instructions are carried out inside the same process. Instructions vary based on the specific operation that needs to be performed. These range from simple tasks like an application-level restart, to more complex ones such as tearing down an underlying database's node if the user wants to pause an active instance to save costs on an unused resource.&lt;/p&gt;
&lt;p&gt;Each high-level instruction is broken down into smaller, composable granular tasks. Thus, the provisioner coordinates resources at a relatively low level, explicitly managing k8s primitives like StatefulSets, Deployments, Ingresses, Services, and PersistentVolumes as well as AWS resources like EC2 Instances, Security Groups, Route53 Records, and EBS Volumes.&lt;/p&gt;
&lt;p&gt;Provisioning progress is reported back to the application through a separate Kafka queue (we call it the "gossip" queue). Backend workers are listening to this queue for new messages, and update a customer database's status in our backend database when new information has been received.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Provisioner Workflow" src="/img/blog/questdb-cloud-operator/provisioner-flowchart.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Each granular operation also has a corresponding rollback procedure in case the provisioner experiences an error while running a set of commands. These rollbacks are designed to leave the target database's infrastructure in a consistent state, although it's important to note that this is not the desired state as intended by the end user's actions. Once an error has been encountered and the rollback executed, the backend will be notified about the original error through the gossip Kafka queue in order to surface any relevant information back to the end user. Our internal monitoring system will also trigger alerts on these errors so an on-call engineer can investigate the issue and kick off the appropriate remediation steps.&lt;/p&gt;
&lt;p&gt;While this system has proven to be reliable over the past several months since we &lt;a href="https://twitter.com/nicolas_hrd/status/1641770344468946944" rel="external"&gt;first launched our public cloud offering&lt;/a&gt;, the framework still leaves room for improvement. Much of our recent database development work around High Availability and Cold Storage requires adding more infrastructure components to our existing system. And adding new components to any distributed system tends to increase the system's overall error rate.&lt;/p&gt;
&lt;p&gt;As we continue to add complexity to our infrastructure, there is a risk that the above rollback strategy won't be sufficient enough to recover from new classes of errors. We need a more responsive and dynamic provisioning system that can automatically respond to faults and take proactive remediation action, instead of triggering alerts and potentially waking up one of our globally-distributed engineers.&lt;/p&gt;
&lt;p&gt;This is where a Kubernetes operator can pick up the slack and improve our existing infrastructure automation.&lt;/p&gt;
&lt;h3 id="introducing-a-kubernetes-operator"&gt;Introducing a Kubernetes Operator&lt;/h3&gt;
&lt;p&gt;Now that stateful workloads have started to mature in the Kubernetes ecosystem, practitioners are writing operators to automate the management of these workloads. Operators that handle full-lifecycle tasks like provisioning, snapshotting, migrating, and upgrading clustered database systems have started to become mainstream across the industry.&lt;/p&gt;
&lt;p&gt;These operators include Custom Resource Definitions (CRDs) that define new types of Kubernetes API resources which describe how to manage the intricacies of each particular database system. Custom Resources (CRs) are then created from these CRDs that effectively define the expected state of a particular database resource. CRs are managed by operator controller deployments that orchestrate various Kubernetes primitives to migrate the cluster state to its desired state based on the CR manifests.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Operator Terminology" src="/img/blog/questdb-cloud-operator/operators.webp" /&gt;&lt;/p&gt;
&lt;p&gt;By writing our own Kubernetes operator, I believed, we could take advantage of this model by allowing the provisioner to only focus on higher-level tasks, while an operator handles the nitty-gritty details of managing each individual customer database. Since an operator is directly hooked in to the Kubernetes API server through Resource Watches, it can immediately react to changes in cluster state and constantly reconcile cluster resources to achieve the desired state described by a Custom Resource spec. This allows us to automate even more of our infrastructure management, since the control plane is effectively running all of the time, instead of only during explicit provisioning actions.&lt;/p&gt;
&lt;p&gt;With an operator deployed in one of our clusters, we can now interact with a customer database by executing API calls and CRUD operations against a single resource, a new Custom Resource Definition that represents an entire QuestDB Cloud deployment. This CRD allows us to maintain a central Kubernetes object that represents the canonical version of the entire state of a customer's QuestDB database deployment in our cluster. It includes fields like the database version, instance type, volume size, server config, and the underlying node image, as well as key pieces of status like a node's IP address and DNS record information. Based on these spec field values, as well as changes to them, the operator performs a continuous reconciliation of all the relevant Kubernetes and AWS primitives to match the expected state of the higher-level QuestDB construct.&lt;/p&gt;
&lt;h3 id="new-architecture"&gt;New Architecture&lt;/h3&gt;
&lt;p&gt;This leads us to our new architecture:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Operator-Based Architecture" src="/img/blog/questdb-cloud-operator/operator-infrastructure.webp" /&gt;&lt;/p&gt;
&lt;p&gt;While this diagram resembles the old one, there is a key difference here; a QuestDB operator instance is now running inside of each cluster! Instead of the provisioner being responsible for all of the low-level Kubernetes and AWS building blocks, it is now only responsible for modifying a single object: a QuestDB Custom Resource. Any modifications to the state of a QuestDB deployment, large or small, can be made by changing a single manifest that describes the desired state of the entire deployment.&lt;/p&gt;
&lt;p&gt;This solution still uses the provisioner, but in a much more limited capacity than before. The provisioner continues to act as the communication link between the application and each tenant cluster, and it is still responsible for coordinating high-level operations. But, it can now delegate low-level database management responsibilities to the operator's constantly-running reconciliation loops.&lt;/p&gt;
&lt;h2 id="operator-benefits"&gt;Operator Benefits&lt;/h2&gt;
&lt;h3 id="auto-healing"&gt;Auto-Healing&lt;/h3&gt;
&lt;p&gt;Since the operator model is eventually-consistent, we no longer need to rely on rollbacks in the face of errors. Instead, if the operator encounters an error in its reconciliation loop, it re-attempts that reconciliation at a later time, using an exponential backoff by default.&lt;/p&gt;
&lt;p&gt;The operator is registered to Resource Watches for all QuestDBs in the cluster, as well as for their subcomponents. So, a change in any of these components, or the QuestDB spec itself, will trigger a reconciliation loop inside the operator. If a component is accidentally mutated or experiences some state drift, the operator will be immediately notified of this change and automatically attempt to bring the rest of the infrastructure to its desired state.&lt;/p&gt;
&lt;p&gt;This property is incredibly useful to us, since we provision a dedicated node for each database and use a combination of taints and NodeSelectors to ensure that all of a deployment's Pods are running on this single node. Due to these design choices, we are now exposed to the risk of a node hardware failure that renders it inaccessible. Typical Kubernetes recovery patterns can not save us.&lt;/p&gt;
&lt;p&gt;So instead of using an alerting-first remediation paradigm, where on-call engineers would follow a runbook after receiving an alert that a database is down, the operator allows us to write custom node failover logic that is triggered as soon as the cluster is aware of an unresponsive node. Now, our engineers can sleep soundly through the night while the operator automatically performs the remediation steps that they would previously be responsible for.&lt;/p&gt;
&lt;h3 id="simpler-provisioning"&gt;Simpler Provisioning&lt;/h3&gt;
&lt;p&gt;Instead of having to coordinate tens of individual objects in a typical operation, the provisioner now only has to modify a single Kubernetes object while executing higher level instructions. This dramatically reduces the code complexity inside of the provisioner codebase and makes it far easier for us to write unit and integration tests. It is also easier for on-call engineers to quickly diagnose and solve issues with customer databases, since all of the relevant information about a specific database is contained inside of a single Kubernetes spec manifest, instead of spread across various resource types inside each namespace. Although, as expressed in the previous point, hopefully there will be fewer problems for our on-call engineers to solve in the first place!&lt;/p&gt;
&lt;h3 id="easier-upgrades"&gt;Easier Upgrades&lt;/h3&gt;
&lt;p&gt;The 15-week Kubernetes release cadence can be a burden for smaller infrastructure teams to keep on top of. This relatively rapid schedule, combined with the short lifetime of each release, means that it is critical for organisations to keep their clusters up-to-date. The importance of this is magnified if you're not running your own control plane, since vendors tend to drop support for old k8s versions fairly quickly.&lt;/p&gt;
&lt;p&gt;&lt;img alt="EKS End of Life Schedule" src="/img/blog/questdb-cloud-operator/endoflife.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Since we're running each database on its own node, and there are only two of us to manage the entire QuestDB Cloud infrastructure, we could easily spend most of our time just upgrading customer database nodes to new Kubernetes versions. Instead, our operator now allows us to automatically perform a near-zero-downtime node upgrade with a single field change on our CRD.&lt;/p&gt;
&lt;p&gt;Once the operator detects a change in a database's underlying node image, it will handle all of the orchestration around the upgrade, including keeping the customer database online for as long as possible and cleaning up stale resources once the upgrade is complete. With this automation in place, we can now perform partial upgrades against subsets of our infrastructure to test new versions before rolling them out to the entire cluster, and also seamlessly roll back to an older node version if required.&lt;/p&gt;
&lt;h3 id="control-plane-locality"&gt;Control Plane Locality&lt;/h3&gt;
&lt;p&gt;By deploying an operator-per-cluster, we can improve the time it takes for provisioning operations to complete by avoiding most cross-region traffic that takes place from the provisioner to remote tenant clusters. We only need to make a few cross-region API calls to kickstart the provisioning process by performing a CRUD operation on a QuestDB spec, and the local cluster operator will handle the rest. This improves our API call network latency and reliability, reducing the number of retries for any given operation as well as the overall database provisioning time.&lt;/p&gt;
&lt;h3 id="resource-coordination"&gt;Resource Coordination&lt;/h3&gt;
&lt;p&gt;Now that we have a single Kubernetes resource to represent an entire QuestDB deployment, we can use this resource as a primitive to build even larger abstractions. For example, now we can easily create a massive (but temporary) instance to crunch large amounts of data, save that data to a PersistentVolume in the cluster, then spin up another smaller database to act as the query interface. Due to the inherent composability of Kubernetes resources, the possibilities with a QuestDB Custom Resource are endless!&lt;/p&gt;
&lt;h2 id="the-devops-stuff"&gt;The DevOps Stuff&lt;/h2&gt;
&lt;h3 id="testing-the-operator"&gt;Testing the Operator&lt;/h3&gt;
&lt;p&gt;Before deploying such a significant piece of software in our stack, I wanted to be as confident as possible in its behavior and runtime characteristics before letting it run wild in our clusters. So we wrote and ran a large series of unit and integration tests before we felt confident enough to deploy the new infrastructure to development, and eventually to production.&lt;/p&gt;
&lt;p&gt;Unit tests were written against an in-memory Kubernetes API server using the &lt;a href="https://book.kubebuilder.io/reference/envtest.html" rel="external"&gt;controller-runtime/pkg/envtest&lt;/a&gt; library. Envtest allowed us to iterate quickly since we could run tests against a fresh API cluster that started up in around 5 seconds instead of having to spin up a new cluster every time we wanted to run a test suite. Even existing micro-cluster tools like &lt;a href="https://kind.sigs.k8s.io/" rel="external"&gt;Kind&lt;/a&gt; could not get us that level of performance. Since envtest is also not packaged with any controllers, we could also set our test cluster to a specific state and be sure that this state would not be modified unless we explicitly did so in our test code. This allowed us to fully test specific edge-cases without having to worry about control plane-level controllers modifying various objects out from underneath us.&lt;/p&gt;
&lt;p&gt;But unit testing is not enough, especially since many of the primitives that we are manipulating are fairly high-level abstractions in and of themselves. So we also spun up a test EKS cluster to run live tests against, built with the same manifests as our development and production environments. This cluster allowed us run tests using live AWS and K8s APIs, and was integral when testing features like node recovery and upgrades.&lt;/p&gt;
&lt;p&gt;We were also able to leverage &lt;a href="https://onsi.github.io/ginkgo/" rel="external"&gt;Ginkgo&lt;/a&gt;'s parallel testing runtime to run our integration tests on multiple concurrent processes. This provided multiple benefits: we could run our entire integration test suite in under 10 minutes and also reuse the same suite to load test the operator in a production-like environment. Using these tests, we were able to identify hot spots in the code that needed further optimization and experimented with ways to save API calls to ease the load on our own Kubernetes API server while also staying under various AWS rate limits. It was only after running these tests over and over again that I felt confident enough to deploy the operator to our dev and prod clusters.&lt;/p&gt;
&lt;h3 id="monitoring"&gt;Monitoring&lt;/h3&gt;
&lt;p&gt;Since we built our operator using the &lt;a href="https://book.kubebuilder.io/" rel="external"&gt;Kubebuilder framework&lt;/a&gt;, most standard monitoring tasks were handled for us out-of-the-box. Our operator automatically exposes a rich set of Prometheus metrics that measure reconciliation performance, the number of k8s API calls, workqueue statistics, and memory-related metrics. We we were able to ingest these metrics into pre-built dashboards by leveraging the &lt;a href="https://book.kubebuilder.io/plugins/grafana-v1-alpha" rel="external"&gt;grafana/v1-alpha plugin&lt;/a&gt;, which scaffolds two Grafana dashboards to monitor Operator resource usage and performance. All we had to do was add these to our existing Grafana manifests and we were good to go!&lt;/p&gt;
&lt;h3 id="migrating-to-the-new-paradigm"&gt;Migrating to the New Paradigm&lt;/h3&gt;
&lt;p&gt;No article about a large infrastructure change would be complete without a discussion about the migration strategy from old to the new. In our case, we designed the operator to be a drop-in-replacement for the database instances managed by the provisioner. This involved many tedious rounds of diffing yaml manifests to ensure that the resources coordinated by the operator would match the ones that were already in our cluster.&lt;/p&gt;
&lt;p&gt;Once this resource parity was reached, we utilized Kubernetes owner-dependent relationships to create an Owner Reference on each sub-resource that pointed to the new QuestDB Custom Resource. Part of the reconciliation loop involves ensuring that these ownership references are in place, and if not, are created automatically. This way, the operator can seamlessly "inherit" legacy resources through its main reconciliation codepath.&lt;/p&gt;
&lt;p&gt;We were able to roll this out incrementally due to the guarantee that the operator cannot make any changes to resources that are not owned by a QuestDB CR. Because of this property, we could create a single QuestDB CR and monitor the operator's behavior on only the cluster resources associated with that database. Once everything behaved as expected, we started to roll out additional Custom Resources. This could be easily controlled on the provisioner side; by checking for the existence of a QuestDB CR for a given database. If the CR existed, then we could be sure that we were dealing with an operator-managed instance, otherwise the provisioner would behave as it did before the operator existed.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I'm extremely proud of the hard work that we've put into this project. Starting from an empty git repo, it's been a long journey to get here. There have been several times over the past few months when I questioned if all of this work was even worth the trouble, especially since we already had a provisioning system that was battle-tested in production. But with the support of my colleagues, I powered through to the end. And I can definitely say that it's been worth it, as we've already seen immediate benefits of moving to this new paradigm!&lt;/p&gt;
&lt;p&gt;Using our QuestDB CR as a building block has paved the way for new features like adding a large-format bulk CSV upload with our &lt;a href="https://questdb.io/docs/guides/importing-data/" rel="external"&gt;SQL COPY command&lt;/a&gt;. The new node upgrade process will save us countless hours with every new k8s release. And even though we haven't experienced a hardware failure yet, I'm confident that our new operator will seamlessly handle that situation as well, without paging up an on-call engineer. Especially since we are a small team, it is critical for us to use automation as a force-multiplier that assists us in managing our Cloud infrastructure, and a Kubernetes operator provides us with next-level automation that was previously unattainable.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Originally posted on the &lt;a href="https://questdb.io/blog/questdb-cloud-kubernetes-operator/" rel="external"&gt;QuestDB Blog&lt;/a&gt;&lt;/p&gt;</description><author>Steven Sklar | My Blog</author><pubDate>Tue, 15 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://sklar.rocks/questdb-cloud-operator/</guid></item><item><title>TIL - How to send an SQS message from a Lambda inside a VPC</title><link>/sqs-inside-vpc-lambda/</link><description>&lt;p&gt;Sending a message to SQS from a Lambda inside a VPC should be trivial. Unfortunately, this is AWS so they like to make it as complex as possible.&lt;/p&gt;
&lt;p&gt;Here is the process to follow if you're stuck:&lt;/p&gt;
&lt;h2&gt;Setup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;The lambda function was in a subnet on the VPC&lt;/li&gt;
&lt;li&gt;Create a new VPC endpoint with a Full Access policy in that same VPC and subnet as the lambda. You will need to create the VPC endpoint first, then click on it, then select the &amp;quot;Policy&amp;quot; tab, then edit the policy.&lt;/li&gt;
&lt;li&gt;Create a security group with HTTPS -&amp;gt; 0.0.0.0/0 Inbound and All traffic outbound.&lt;/li&gt;
&lt;li&gt;Attach this security group to the VPC endpoint.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Sample Lambda Function code&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-ts"&gt;import { SendMessageBatchCommand, SQSClient } from &amp;quot;@aws-sdk/client-sqs&amp;quot;;
import chunk from &amp;quot;lodash/chunk&amp;quot;;
import { logger } from &amp;quot;./logger&amp;quot;;

const BATCH_SIZE = 10;

const sqsClient = new SQSClient({
  region: AWS_REGION,
  endpoint: SQS_VPC_ENDPOINT || null,
});

export const addToQueue = async (
  messages: Array&amp;lt;{ type: string; data: Record&amp;lt;string, string&amp;gt; }&amp;gt;
): Promise&amp;lt;void&amp;gt; =&amp;gt; {
  logger.debug(
    `Sending ${messages.length} emails. Data: ${JSON.stringify(messages)}`
  );
  const batches = chunk(messages, BATCH_SIZE);

  logger.debug(`Sending email to ${QUEUE_URL}`);

  await Promise.all(
    batches.map(async (batch) =&amp;gt; {
      const command = new SendMessageBatchCommand({
        QueueUrl: QUEUE_URL,
        Entries: batch.map((message, index) =&amp;gt; ({
          Id: String(index),
          MessageBody: JSON.stringify(message),
        })),
      });

      try {
        await sqsClient.send(command);
        logger.debug(`Sent SQS email(s) to queue`);
      } catch (err) {
        logger.error(`Error when queueing email: ${JSON.stringify(err)}`);
      }
    })
  );
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adapted from the answer here: &lt;a href="https://github.com/aws/aws-sdk-js/issues/3203#issuecomment-786372586"&gt;https://github.com/aws/aws-sdk-js/issues/3203#issuecomment-786372586&lt;/a&gt;&lt;/p&gt;</description><author/><pubDate>Tue, 15 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">/sqs-inside-vpc-lambda/</guid></item><item><title>Feeding GPTBot</title><link>https://tsak.dev/posts/gptbot/</link><description>&lt;p&gt;With the &lt;a href="https://arstechnica.com/information-technology/2023/08/openai-details-how-to-keep-chatgpt-from-gobbling-up-website-data/"&gt;recent news&lt;/a&gt;
of OpenAI&amp;rsquo;s web crawler &lt;a href="https://platform.openai.com/docs/gptbot"&gt;respecting&lt;/a&gt; &lt;code&gt;robots.txt&lt;/code&gt; and the
&lt;a href="https://hn.algolia.com/?dateRange=all&amp;amp;page=0&amp;amp;prefix=true&amp;amp;query=gptbot&amp;amp;sort=byPopularity&amp;amp;type=story"&gt;ensuing scramble by seemingly everybody&lt;/a&gt;
ensuring their &lt;code&gt;robots.txt&lt;/code&gt; is blocking GPTBot, I was thinking if there wasn&amp;rsquo;t a better solution to help our future AI overlords make sense
of the world.&lt;/p&gt;
&lt;p&gt;As I am hosting all my sites on a tiny NUC using &lt;a href="https://docs.nginx.com/nginx/admin-guide/web-server/"&gt;nginx&lt;/a&gt; and having
&lt;a href="https://tsak.dev/posts/use-nginx-as-a-hacker-business-card/"&gt;previously played&lt;/a&gt; with its &lt;a href="http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#return"&gt;return&lt;/a&gt;
directive I decided to reuse the same trick for visits of GPTBot.&lt;/p&gt;
&lt;p&gt;I want to send the following response to GPTBot every time nginx sees its user agent string.&lt;/p&gt;</description><author>Look mum, I have a blog on tsak.dev</author><pubDate>Tue, 15 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://tsak.dev/posts/gptbot/</guid></item><item><title>How would you like ChatGPT to respond?</title><link>https://taylor.town/chatgpt-custom</link><description>Emulate clear writers like Steinbeck, Hemingway, Orwell.</description><author>taylor.town</author><pubDate>Tue, 15 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/chatgpt-custom</guid></item><item><title>Import Polar trainingdata to Strava</title><link>https://dannas.name/2023/08/15/import-polar-trainingdata-to-strava</link><description>When you sync data between your Polar account and Strava you don’t get your full training history imported. I used some online tools and wrote a python script to to a full export</description><author>Daniel Näslund</author><pubDate>Tue, 15 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://dannas.name/2023/08/15/import-polar-trainingdata-to-strava</guid></item><item><title>Thinking about functional programming</title><link>http://notes.eatonphil.com/2023-08-15-thinking-about-functional-programming.html</link><description>&lt;p&gt;Someone on Discord asked about how to learn functional programming.&lt;/p&gt;
&lt;p&gt;The question and my initial tweet on the subject prompted an
interesting
&lt;a href="https://twitter.com/ShriramKMurthi/status/1691548254331092992"&gt;discussion&lt;/a&gt;
with &lt;a href="https://twitter.com/ShriramKMurthi"&gt;Shriram
Krishnamurthi&lt;/a&gt; and other folks.&lt;/p&gt;
&lt;p&gt;So here's a slightly more thought out exploration.&lt;/p&gt;
&lt;p&gt;And just for backstory sake: I spent a few years a while
ago &lt;a href="https://github.com/eatonphil/ponyo"&gt;programming in
Standard ML&lt;/a&gt; and I wrote
a &lt;a href="https://github.com/eatonphil/bsdscheme"&gt;chunk of a Scheme
  implementation&lt;/a&gt;. I'm not an expert, but I have a bit of background.&lt;/p&gt;
&lt;p&gt;Hey, this is a free opinion.&lt;/p&gt;
&lt;h3 id="concepts-from-functional-programming"&gt;Concepts from functional programming&lt;/h3&gt;&lt;p&gt;When people talk about functional programming, I think of a few key
choices you can make while programming:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Immutability by default&lt;/li&gt;
&lt;li&gt;(Tail) recursion by default&lt;/li&gt;
&lt;li&gt;First-class functions (and the suite of tools that go along with it. e.g. map, reduce/fold)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And if you have experience as a programmer, you either get the basic
gist of these tenets or you can easily read about the basics.&lt;/p&gt;
&lt;p&gt;That is, while most programmers I've met understood the basics, most
programmers I've met were not particularly &lt;em&gt;comfortable&lt;/em&gt; or &lt;em&gt;fluent&lt;/em&gt;
expressing programs with these ideas.&lt;/p&gt;
&lt;p&gt;For myself, the only way I got comfortable expressing code with these
ideas was lots of practice (as I mentioned above). And yet,
even after I did a bunch of programming in Standard ML and Scheme, I
really didn't see a particular benefit to practicing in a language
other than one with which I wa already generally comfortable.&lt;/p&gt;
&lt;p&gt;You have to learn a lot of other random things when you pick up Scheme
or Standard ML that aren't just: practice immutability by default,
recursion, and first-class functions.&lt;/p&gt;
&lt;p&gt;So I think it's kind of misguided when person A asks how to learn
functional programming and person B responds that they should learn
Haskell or OCaml or whatever. I see this happen pretty often online.&lt;/p&gt;
&lt;p class="note"&gt;
  Beyond any "language for functional programming" as a recommendation
  in general, Haskell is a particularly egregious suggestion to make
  in my opinion because not only are you trying to practice functional
  programming tenets but you're also dealing with a complex type
  system and lazy evaluation.
&lt;/p&gt;&lt;p&gt;Instead, &lt;a href="https://notes.eatonphil.com/practicing-recursion.html"&gt;practice immutability, recursion,
map/reduce&lt;/a&gt; in
whatever language you like.&lt;/p&gt;
&lt;h3 id="programming-languages"&gt;Programming languages&lt;/h3&gt;&lt;p&gt;If you want to study programming languages, that's awesome. However,
functional programming doesn't really have any direct connection to
studying programming languages.&lt;/p&gt;
&lt;p&gt;Languages are all over the place. Scheme, Standard ML, and Haskell are
worlds apart, even within the functional programming family.&lt;/p&gt;
&lt;p&gt;And modern languages have mostly adapted the aspects of functional
programming that used to be unique 20 years ago.&lt;/p&gt;
&lt;p&gt;Moreover, there are many other worthwhile families of languages to
learn about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Imperative/C-like (ok, you probably already know these)&lt;/li&gt;
&lt;li&gt;Stack-based (JVM, x86 assembly sort of, Forth)&lt;/li&gt;
&lt;li&gt;Array-oriented (APL, J)&lt;/li&gt;
&lt;li&gt;Declarative (CSS, SQL, TLA+, Prolog)&lt;/li&gt;
&lt;li&gt;Data (HTML, JSON, YAML)&lt;/li&gt;
&lt;li&gt;Proof assistants (Isabelle/HOL, Coq)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The list isn't exhaustive, and the variations within families can be
massive. But the point is that functional programming doesn't mean
crazy programming languages or crazy programming ideas. Functional
programming is a &lt;em&gt;subset&lt;/em&gt; of crazy programming languages and crazy
programming ideas.&lt;/p&gt;
&lt;p&gt;If you want to learn about crazy programming languages and crazy
programming ideas, you should! Go for it!&lt;/p&gt;
&lt;h3 id="introduction-to-computer-science"&gt;Introduction to Computer Science&lt;/h3&gt;&lt;p&gt;SICP is famous as the (former) introductory textbook for computer
science at MIT, and for its use of Scheme and the &lt;a href="https://en.wikipedia.org/wiki/Meta-circular_evaluator"&gt;Metacircular
Evaluator&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I don't have any experience teaching beginners how to program so I
don't have thoughts on if this made sense. That's for folks like
&lt;a href="https://twitter.com/ShriramKMurthi/status/1691548254331092992"&gt;Shriram&lt;/a&gt;
to think about.&lt;/p&gt;
&lt;p&gt;However, I'm a half-decent programmer and I can't make it through this
book. If you liked the book or want to read it, that's great! But I &lt;a href="https://notes.eatonphil.com/recommending-a-book.html"&gt;don't
recommend&lt;/a&gt; it to
anyone.&lt;/p&gt;
&lt;p&gt;And many introductory Computer Science textbooks just don't make much
sense to give to experienced programmers. For an experienced
programmer, they can be quite slow!&lt;/p&gt;
&lt;p&gt;Most of the folks I see asking about how to learn functional
programming are experienced programmers.&lt;/p&gt;
&lt;h3 id="do-whatever-you-feel-like-doing"&gt;Do whatever you feel like doing&lt;/h3&gt;&lt;p&gt;I don't mean to overanalyze things, or get you overanalyzing
things. If you want to learn functional programming by writing
Haskell, that's awesome, you should go for it.&lt;/p&gt;
&lt;p&gt;Wanting to do something is basically the best motivation there is.&lt;/p&gt;
&lt;p&gt;The only reason I write this sort of post is so that folks who think
that using Haskell or Standard ML or Scheme or reading SICP is the
only way to learn functional programming see those ideas aren't
necessarily true.&lt;/p&gt;
&lt;h3 id="write-a-scheme!"&gt;Write a Scheme!&lt;/h3&gt;&lt;p&gt;Finally, for folks with time and motivation wanting to seriously work
out their functional programming muscles, writing a Scheme
implementation with a decent chunk of the standard library can be an
immensely enjoyable project.&lt;/p&gt;
&lt;p&gt;You'll learn a lot about languages and compilers and algorithms and
data structures. It's leetcode with meaning.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Wrote a short post on this idea about different things to think about when talking about learning functional programming&lt;br /&gt;&lt;br /&gt;1. Core concepts (immutability, first-class functions, recursion)&lt;br /&gt;&lt;br /&gt;2. Exploring programming languages&lt;br /&gt;&lt;br /&gt;3. Teaching CS to students&lt;a href="https://t.co/k4LzvnHbNs"&gt;https://t.co/k4LzvnHbNs&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1691617741764018430?ref_src=twsrc%5Etfw"&gt;August 16, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Tue, 15 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2023-08-15-thinking-about-functional-programming.html</guid></item><item><title>My Favourite macOS Terminal Commands and Tools</title><link>https://www.chriswales.uk/blog/my-favourite-macos-terminal-commands-and-tools</link><description>Like many software engineers, I spend a decent
amount of time on the command line. It’s an invaluable tool that I use
daily and one that I…</description><author>Christopher Wales' Blog</author><pubDate>Tue, 15 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.chriswales.uk/blog/my-favourite-macos-terminal-commands-and-tools</guid></item><item><title>Godot4 HTML5 Export Development Server</title><link>https://ztoz.blog/posts/godot4-html5-export/</link><description>&lt;p&gt;Godot 4&amp;rsquo;s &lt;a href="https://docs.godotengine.org/en/4.1/tutorials/export/exporting_for_web.html"&gt;HTML5 export&lt;/a&gt; uses WebAssembly, WebGL, and SharedArrayBuffers. Browsers require a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts"&gt;secure context&lt;/a&gt; for these features to be available, which requires sending certain HTTP headers when serving the game content. For development, there are two straight-forward ways to serve the content: Godot&amp;rsquo;s Python http server and Miniserve.&lt;/p&gt;
&lt;h1 id="godots-python-http-server"&gt;Godot&amp;rsquo;s Python HTTP Server&lt;/h1&gt;
&lt;p&gt;The Godot team has written a script, leveraging Python&amp;rsquo;s existing http server implementation, which adds the necessary HTTP headers.&lt;/p&gt;
&lt;p&gt;Download &lt;a href="https://raw.githubusercontent.com/godotengine/godot/master/platform/web/serve.py"&gt;&lt;code&gt;serve.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Run it via:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-sh"&gt;python3 serve.py -r game_directory
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="miniserve"&gt;Miniserve&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://github.com/svenstaro/miniserve"&gt;Miniserve&lt;/a&gt; is a self-contained command-line tool for serving HTTP content. As a self-contained binary, it may be easier to install and manage than a full Python installation.&lt;/p&gt;
&lt;p&gt;You can run it via:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-sh"&gt;./miniserve --header Cross-Origin-Embedder-Policy:require-corp --header Cross-Origin-Opener-Policy:same-origin game_directory
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><author>ℤ→ℤ</author><pubDate>Mon, 14 Aug 2023 23:05:53 GMT</pubDate><guid isPermaLink="true">https://ztoz.blog/posts/godot4-html5-export/</guid></item><item><title>Managing difficult software engineers</title><link>https://vadimkravcenko.com/shorts/managing-bad-engineers/</link><description>&lt;p&gt;Question:&lt;/p&gt;
&lt;/p&gt;
&lt;p&gt; Answer:&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://vadimkravcenko.com/shorts/managing-bad-engineers/"&gt;Managing difficult software engineers&lt;/a&gt; appeared first on &lt;a href="https://vadimkravcenko.com"&gt;Vadim Kravcenko&lt;/a&gt;.&lt;/p&gt;</description><author>Vadim Kravcenko</author><pubDate>Mon, 14 Aug 2023 15:22:09 GMT</pubDate><guid isPermaLink="true">https://vadimkravcenko.com/shorts/managing-bad-engineers/</guid></item><item><title>4000 Weeks: Time Management for Mortals by Oliver Burkeman</title><link>https://apurva-shukla.me/bookshelf/4000-weeks/</link><description>⭐ ⭐ ⭐ ⭐ ⭐ Four Thousand Weeks is in my opinion a must-read. It covers a breadth of topics, some of which I’ll detail below.

“Life, I knew…</description><author>Apurva Shukla's RSS Feed</author><pubDate>Mon, 14 Aug 2023 13:51:10 GMT</pubDate><guid isPermaLink="true">https://apurva-shukla.me/bookshelf/4000-weeks/</guid></item><item><title>Why I Undercook My Poultry</title><link>https://matteoraso.github.io/why-i-undercook-my-poultry.html</link><description>&lt;p&gt;The title is partially clickbait: I only undercook the white meat. Because dark meat is full of fat and connective tissue, it needs to be very well cooked to get a nice texture. White meat doesn't have that, so it's very prone to drying out if you overcook it. I'm …&lt;/p&gt;</description><author>Matteo's Website</author><pubDate>Mon, 14 Aug 2023 07:00:00 GMT</pubDate><guid isPermaLink="true">https://matteoraso.github.io/why-i-undercook-my-poultry.html</guid></item><item><title>Symbolic links in Python are easier</title><link>https://heitorpb.github.io/bla/symbolic-links-in-python-are-easier/</link><description>How to create symbolic links in Python. Easier than using ln.</description><author>Heitor's log</author><pubDate>Mon, 14 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://heitorpb.github.io/bla/symbolic-links-in-python-are-easier/</guid></item><item><title>The Fourth Ideal: Psychological Safety</title><link>https://backendhance.com/blog/2023/the-fourth-ideal-psychological-safety/</link><description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Letzte Woche sind die Emotionen hochgekocht.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Einer meiner Kunden hat etwas außergewöhnliches geschafft. Sie betreiben seit 10 Jahren erfolgreich ein Produkt. Höchst profitabel.&lt;/p&gt;
&lt;p&gt;Aber das Projekt wurde nicht von ausgelernten Entwicklern begonnen. Es startete als Nebenprodukt. Die ersten Entwickler waren Queereinsteiger. Super interessiert, motiviert, engagiert und Experten in der Domäne.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Und ihnen ist etwas gelungen, was in 9 von 10 Fällen schiefgeht.&lt;/strong&gt; Ihr Produkt wird aktiv genutzt. Kunden zahlen gerne dafür. Es löst ein echtes Problem. In einer Nische, die kaum eine andere Firma besetzen kann.&lt;/p&gt;</description><author>Backendhance</author><pubDate>Mon, 14 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2023/the-fourth-ideal-psychological-safety/</guid></item><item><title>Writing a basic code formatter</title><link>https://ntietz.com/blog/writing-basic-code-formatter/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;I've been working on my programming language for a couple of months now, in fits and starts&lt;sup class="footnote-reference" id="fr-1-1"&gt;&lt;a href="https://ntietz.com/blog/writing-basic-code-formatter/#fn-1"&gt;[1]&lt;/a&gt;&lt;/sup&gt;.
In the &lt;a href="https://ntietz.com/blog/introducing-hurl/"&gt;original post&lt;/a&gt;, I laid out my plan for it, and after creating the parser the next step was writing a formatter.
I thought this would be a nice intermediate step after writing the parser, something easy to exercise the code without being as complicated as the interpreter.&lt;/p&gt;
&lt;p&gt;Well...
It was hard, even with the shortcuts I took.&lt;/p&gt;
&lt;p&gt;The author of Crafting Interpreters once wrote that a formatter was the &lt;a href="https://journal.stuffwithstuff.com/2015/09/08/the-hardest-program-ive-ever-written/"&gt;hardest program he had written&lt;/a&gt;, and now I can see why.
Mine is definitely not as sophisticated as his, and it was difficult to figure out on my own.&lt;/p&gt;
&lt;p&gt;One of the big challenges I ran into was what interface to use for the formatter.
I wound up settling on this trait, along with a companion struct.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span style="font-style: italic; color: #928374;"&gt;/// Trait for types that can be formatted for pretty printing.
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;pub trait &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;Format &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;fmt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;self&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;ctx&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;mut&lt;/span&gt;&lt;span&gt; FormatContext) -&amp;gt; String;
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;pub struct &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;FormatContext &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;pub &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;indent&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;usize&lt;/span&gt;&lt;span&gt;,
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;impl &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;FormatContext &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;pub fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;indent_incr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;mut &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;self&lt;/span&gt;&lt;span&gt;) {
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;self&lt;/span&gt;&lt;span&gt;.indent &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;+= &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;4&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;pub fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;indent_decr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;mut &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;self&lt;/span&gt;&lt;span&gt;) {
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;self&lt;/span&gt;&lt;span&gt;.indent &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;-= &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;4&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In an early iteration I was passing through a &lt;code&gt;Write&lt;/code&gt; object directly, and writing into it as I went.
The issue with doing that was that I could only do one forward pass through the code, so if I ever wanted to limit line lengths, I couldn't!
When I wrote things, I would only know the local content and not what came before or after it.&lt;/p&gt;
&lt;p&gt;So, instead I went for returning a &lt;code&gt;String&lt;/code&gt; and building it iteratively.
This is perhaps not the most efficient choice, but optimization is for future-Nicole.
She loves that shit, so that's kind of a gift to my future self.&lt;/p&gt;
&lt;p&gt;I also had to make sure to include some context inside each format call.
I originally passed in the indentation level directly, but quickly moved that into a mutable context variable.
Placing it inside a context struct allows me to more easily add more variables than if I have to add them to each implementation of the trait.
Right now the context only contains the indentation level, but could potentially contain more information, such as line lengths or format settings.&lt;/p&gt;
&lt;p&gt;After that, it was a matter of just kind of chugging through and having it write out what each different piece of the tree corresponds to.
Here's an abridged version of what formatting a &lt;code&gt;Stmt&lt;/code&gt; looks like.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #fa5c4b;"&gt;impl &lt;/span&gt;&lt;span&gt;Format &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;for &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;Stmt &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;fmt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;self&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;ctx&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;mut&lt;/span&gt;&lt;span&gt; FmtCtx) -&amp;gt; String {
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; tab &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot; &amp;quot;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;repeat&lt;/span&gt;&lt;span&gt;(ctx.indent);
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;match &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;self &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;            Stmt::Import(expr) &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;                &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;format!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;{}&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;include &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;{}&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;;&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot; &amp;quot;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;repeat&lt;/span&gt;&lt;span&gt;(ctx.indent), expr.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;fmt&lt;/span&gt;&lt;span&gt;(ctx))
&lt;/span&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;span&gt;            Stmt::Declaration(ident, expr) &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;                &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; expr &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&lt;/span&gt;&lt;span&gt; expr.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;fmt&lt;/span&gt;&lt;span&gt;(ctx);
&lt;/span&gt;&lt;span&gt;                &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;format!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;{}&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;let &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;{}&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt; = &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;{}&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;;&amp;quot;&lt;/span&gt;&lt;span&gt;, tab, ident.&lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0&lt;/span&gt;&lt;span&gt;, expr,)
&lt;/span&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span style="font-style: italic; color: #928374;"&gt;// ... SNIP! ...
&lt;/span&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One of the trickiest parts was handling blank lines.
My syntax tree did not include these originally, and my parser stripped out all whitespace.
What to do?
I tried two approaches.&lt;/p&gt;
&lt;p&gt;First I tried integrating blank lines into the grammar and parsing it out, so that I could just directly print them.
This was a detour, and it was very messy and never worked right.
Ultimately, I had to abandon this path because there was no clean way to get it working.
The messy way would have involved updating every single part of my parser.
No &lt;em&gt;thank you&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Then I stumbled into the more correct (less wrong?) way of doing it.
I used the line numbers provided while parsing!
If these line numbers differed by more than 1, I knew&lt;sup class="footnote-reference" id="fr-2-1"&gt;&lt;a href="https://ntietz.com/blog/writing-basic-code-formatter/#fn-2"&gt;[2]&lt;/a&gt;&lt;/sup&gt; that there were extra blank lines between the two elements, so I emitted a &lt;code&gt;BlankLine&lt;/code&gt; element in addition to whatever I was parsing.&lt;/p&gt;
&lt;p&gt;This is a kludge in some ways, because there are edge cases (like the one in a footnote).
I think that the right way to do this is actually to include the line number information on the tokens themselves, and have more information than just the starting line number.
Where does a function start and end, for example?
But it works for now, and it allows me to potentially use the same tree for both the interpreter and the formatter.
This decision may not last forever, but it saves some time now.&lt;/p&gt;
&lt;p&gt;There are a few things I skipped over for the sake of keeping the formatter simple.
The main one is line length.
Like the Go formatter, I just decided to let lines be as long as you want them to be, since that means I never need to deal with wrapping lines: one statement, one line.
I also didn't ever collapse blocks, they always span two lines.
And if you have a comment at the end of a line it gets shoved onto the next line.
Oh, and there's no semblance of proper error handling...&lt;/p&gt;
&lt;p&gt;A few of these I would like to fix later on (like the comment formatting and error handling), but others I don't really care about (line length).
I'd also like to extend it to have more command-line options so it can format in-place instead of printing to standard out, but I'll probably work on that when I have the interpreter running since it won't matter until then.&lt;/p&gt;
&lt;p&gt;And now it runs!
We can pass in a messy program like this:&lt;/p&gt;
&lt;pre class="language-hurl " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-hurl"&gt;&lt;span&gt;let year = 2023; let greeting =
&lt;/span&gt;&lt;span&gt;    &amp;quot;Hurl was created in &amp;quot; + year + &amp;quot;!&amp;quot;
&lt;/span&gt;&lt;span&gt;    ;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;let p = (func(x) { print(x); });
&lt;/span&gt;&lt;span&gt;p(
&lt;/span&gt;&lt;span&gt;    greeting);
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And get out a clean program like this!&lt;/p&gt;
&lt;pre class="language-hurl " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-hurl"&gt;&lt;span&gt;let year = 2023;
&lt;/span&gt;&lt;span&gt;let greeting = &amp;quot;Hurl was created in &amp;quot; + year + &amp;quot;!&amp;quot;;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;let p = (func(x) {
&lt;/span&gt;&lt;span&gt;    print(x);
&lt;/span&gt;&lt;span&gt;});
&lt;/span&gt;&lt;span&gt;p(greeting);
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As usual, the code is in &lt;a href="https://git.sr.ht/~ntietz/hurl-lang/tree"&gt;the repo&lt;/a&gt; if you want to take a look.&lt;/p&gt;
&lt;p&gt;This project was harder than I anticipated, and I also learned a lot more than I expected to.
And now, like all serious languages, Hurl has a formatter.
Next up is the interpreter and a standard library.
After that... maybe a language server, and a package manager?&lt;/p&gt;
&lt;hr /&gt;
&lt;hr /&gt;&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-1"&gt;
&lt;p&gt;I work full-time, write as a hobby, and have two young kids at home. Free time is limited. &lt;a href="https://ntietz.com/blog/writing-basic-code-formatter/#fr-1-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-2"&gt;
&lt;p&gt;I think there are edge cases where this is not true, like if you have two functions which butt up against each other. They end up as siblings in the parse tree but they're more than 1 line apart, so my mechanism would detect blank lines here. This is okay for my formatter (I want blank lines there) but it's a happy accident, and I'm not happy about it. &lt;a href="https://ntietz.com/blog/writing-basic-code-formatter/#fr-2-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 14 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/writing-basic-code-formatter/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>gpt'day mate</title><link>https://taylor.town/gpt-day-mate</link><description>Please prioritize today's task list.</description><author>taylor.town</author><pubDate>Mon, 14 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/gpt-day-mate</guid></item><item><title>The Double It and Pass It On License</title><link>https://kinduff.com/2023/08/14/the-double-it-and-pass-it-on-license/</link><description>The Double It and Pass It On License (DIPOL), Version 1.0</description><author>Alejandro AR (kinduff)</author><pubDate>Mon, 14 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://kinduff.com/2023/08/14/the-double-it-and-pass-it-on-license/</guid></item><item><title>Climb Every Mountain, Run Every Test</title><link>https://xavd.id/blog/post/run-every-test/</link><description>My journey to create a universal test runner&lt;br /&gt;&lt;br /&gt;&lt;a href="https://xavd.id/blog/post/run-every-test/"&gt;Read the whole thing&lt;/a&gt;.</description><author>The David Brownman Blog</author><pubDate>Mon, 14 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xavd.id/blog/post/run-every-test/</guid></item><item><title>The human body is just a PHP server</title><link>https://yusuf.fyi/posts/human-body-is-php-server/</link><description>A random thought I had while having lunch</description><author>Structs &amp;amp; Company | Yusuf Bouzekri</author><pubDate>Mon, 14 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://yusuf.fyi/posts/human-body-is-php-server/</guid></item><item><title>Preserving Truth: The Vital Role of Brutal Historical Footage</title><link>https://marctreagan.medium.com/preserving-truth-the-vital-role-of-brutal-historical-footage-df9e57a674f0?source=rss-39742e0e4fea------2</link><description>&lt;p&gt;I was watching the show the Leftover’s and there is a scene where a cult member is stoned to death. It was brutal and I’m someone who is normally unaffected by these things. With how good special effects are now I find it hard to believe that a real stoning could be more gruesome.&lt;/p&gt;&lt;p&gt;It reminded me of the medieval style executions ISIS used to inflict on their victims: beheadings, drownings, and immolation being the most famous. So as a maladjusted 29 year old male I set out to find some of these old videos. The best website for gore stuff always used to be liveleaks.com but I soon discovered it had been shutdown. The founder never explicitly said why but in his final blogpost he stated it is: “too difficult to keep on fighting.” Sites like this find it very hard to generate advertising revenue as Coca Cola doesn’t want their product being associated with ISIS beheadings. So they must survive off of donations alone which is rarely sufficient. They also likely had large legal bills due to the controversial nature of their content.&lt;/p&gt;&lt;p&gt;I tried bestgore.com and some other similar websites and all had recently been shutdown. Then I tried some of the more niche subreddits like r/watchpeopledie and the like and they too were shut down.&lt;/p&gt;&lt;p&gt;If you weren’t already you may be questioning my sanity at this point. Why am I trying so hard to find a video that is only going to upset me. I would ask why you slow down to look at car wrecks as you drive by them.&lt;/p&gt;&lt;p&gt;The answer is that you are bored while driving and want to be entertained. This is also partially the answer for myself but I also like to think there is great historical significance to these videos. If we the people are not allowed to see the first hand evidence of the event itself how do we even know it happened. These are historical events and should be seen by all who wish to see them.&lt;/p&gt;&lt;p&gt;The ISIS killings are pure evil but what about a killing that is widely considered justified. What about the execution of Saddam Hussein? I remember watching the video of his hanging for the first time when I was in 6th or 7th grade. It was shocking but even to my adolescent brain it made sense that this awful man had to be killed. If you scour the internet for this hanging video today, it cannot be found. It has been erased from history.&lt;/p&gt;&lt;p&gt;It’s not a grand conspiracy or anything, a few mundane factors have caused this. The large internet content providers have been told by the government to keep their content PG or they will face legal and legislative wrath. This has resulted in all kinds of hilarious outcomes like facebook’s team of 100 engineers working on an algorithm to identify male vs. female nipples in order to know which images to censor.&lt;/p&gt;&lt;p&gt;The large platforms have chosen to always take the safe route when it comes to content. Allowing them avoid bad media headlines and government intervention so they can remain open to the largest possible audience and make more money. They take a hard line on content that anyone may find offensive. Much of history is offensive. Therefore much of history online gets erased.&lt;/p&gt;&lt;p&gt;When Marie Antoinette and Louis the 16th were executed it was done on display for all to see so the people could know it really happened. Do you think the Jacobins would not have filmed the thousands of guillotine beheadings in the 1790s and distributed it to the entire populace if they had possessed the technology?&lt;/p&gt;&lt;p&gt;How about when we killed Osama Bin Laden in 2011. Obama’s comment in a 60 minutes interview as to why he would not release any photo evidence of the killing: “It’s important for us to make sure that very graphic photos of somebody who was shot in the head are not floating around as an incitement to additional violence or as a propaganda tool.” I understand this reasoning and I’m sure that we did indeed kill Bin Laden but something about all of this seems a little dystopian. We can’t release proof of the most wanted man in the world for the last decade because we might anger some people and potentially cause more attacks.&lt;/p&gt;&lt;p&gt;The last man to design and implement an attack on America soil which resulted in the deaths of around 2,500 people was Admiral Yamamoto in the form of the surprise attack on Pearl Harbor. We took our revenge on him two years later by shooting down his plane. The Japanese still controlled the island so they recovered his body but if we had got there first do you think we would have not published picture evidence.&lt;/p&gt;&lt;p&gt;In 2012 Hollywood made a movie called Zero Dark Thirty which is all about the hunt for and killing of Bin Laden. This movie had a 50 million dollar budget and shows exactly how Bin Laden was killed. He was shot in his large compound in Pakistan where he had been hiding. Isn’t it odd that we are allowed to recreate the event and graphically show the killing but showing pictures of the real dead body is off limits.&lt;/p&gt;&lt;p&gt;The ancient Assyrians used to hang the heads of their enemies from the city walls to show their dominance, something ISIS actually saw fit to bring back in 2014. In the U.S we won’t even release footage proving public enemy number 1 is dead. Perhaps the U.S. is practicing Teddy Roosevelt’s dictum of “speak softly, and carry a big stick.” Perhaps just stating the facts and not feeling the need to provide proof is the best way to flaunt our power.&lt;/p&gt;&lt;p&gt;Maybe it was decided to just sit back and let Hollywood glorify the killing for us. I’m sure the way it is shown in Zero Dark 30 is far cooler than it really went down. The death scene in the movie is the only reference of the killing I have. So my mind simply substitutes it as historical fact. A lie takes the place of the truth due to mere convenience.&lt;/p&gt;&lt;p&gt;You may be asking why does any of this matter? These are historical events that occurred and affected millions of people. Footage of the Vietnam war streaming into every American living room helped put an end to that conflict. Saddam’s capture and killing freed a nation, for a while at least. ISIS’s well known brutality allowed a coalition to form against them and eventually destroy them. Bin Laden’s killing finally gave Americans some sense of justice about what happened on 9/11.&lt;/p&gt;&lt;p&gt;How wild is it to be able to say: no matter who you are, how wealthy you are, how well you hide, or how fast you run, if you kill enough Americans we will expend billions of dollars of resources over decades to find you and kill you.&lt;/p&gt;&lt;p&gt;One of my favorite scenes from the West Wing is about a similar situation. This is president Bartlett’s response to the joint chief’s telling him we need a “proportional response” to the shooting down of an American plane killing dozens of American citizens.&lt;/p&gt;&lt;p&gt;“2000 years ago a roman citizen could walk across the face of the known world free from the fear of molestation. he could walk across the world unharmed cloaked only in the protection of the words “civis romanus,” I am a roman citizen. So great was the retribution of Rome universally understood as certain should harm befall even one of its citizens.”&lt;/p&gt;&lt;p&gt;Bartlett wanted to make the punishment an actual deterrent. Later President Bartlett is asked by an admiral: “what else is there besides a proportional response?” He replies:&lt;/p&gt;&lt;p&gt;“The disproportional response. Let word ring forth from this time and this place gentleman. If you kill and American, any American, we don’t come back with a proportional response. We come back with total disaster”&lt;/p&gt;&lt;p&gt;9/11 killed thousands of Americans and I think finding and killing Bin Laden was worth every penny.&lt;/p&gt;&lt;p&gt;History is simply what happened. Sometimes it was brutal. These events deserve just as much coverage as the parts that weren’t. In fact they deserve more coverage so we can avoid a recurrence of violence. If you are worried about exposing children then tell parents to use the parental control features available on every device. If you treat all of society as children we will forever remain children.&lt;/p&gt;&lt;p&gt;If you are worried about graphic content potentially upsetting people put a label on it. No one should be forced to watch anything just as people should not be forced to not watch anything, as has occurred by effectively banning these videos.&lt;/p&gt;&lt;p&gt;The emotions you feel when you watch a person be executed are not inherently bad feelings. Humans used to go to weekly executions as a form of entertainment in medieval times. They can be stressful feelings but they are feelings which need to be felt. They are strong feelings which can compel one to action. If you witness an unjust execution you can lobby your political leaders for change. If that change doesn’t come you can start a revolution.&lt;/p&gt;&lt;p&gt;The much lampooned line “Those who don’t know history are doomed to repeat it” is more true than ever. Go read your history, go witness the brutality, then do everything in you power to make sure it never happens again.&lt;/p&gt;&lt;p&gt;If I told you I was going to watch an action movie. You wouldn’t see anything wrong with it. But if I said “I’m going to look up murders and executions of famous people” most people would think I am insane. Why is there such a distinction. Visually both appear the same.&lt;/p&gt;&lt;p&gt;Conversely, if I tell people I am going to read a history book which describes the deaths of millions of people they will think it a great thing to do with my time. If I tell them I am going to read a fiction book describing similar content they will be less impressed. For example reading a WW2 book vs. Lord of the Rings. This seems a great paradox to me.&lt;/p&gt;&lt;p&gt;PS:&lt;/p&gt;&lt;p&gt;I finally found what I was looking for: Seegore.com is still operational and has a few ISIS videos. They were really brutal, watch at your own peril. Documentingreality.com which has a historical focus has great footage of the Kennedy assassination along with stuff relating to the Manson murders/Waco/Jonestown. This site seems to be serving a real need as these are all historically significant videos. This is stuff you will never find on youtube.&lt;/p&gt;&lt;img alt="" height="1" src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=df9e57a674f0" width="1" /&gt;</description><author>Stories by Marc Reagan on Medium</author><pubDate>Sun, 13 Aug 2023 18:46:43 GMT</pubDate><guid isPermaLink="true">https://marctreagan.medium.com/preserving-truth-the-vital-role-of-brutal-historical-footage-df9e57a674f0?source=rss-39742e0e4fea------2</guid></item><item><title>MagKnob</title><link>https://ciesie.com/project/magknob/</link><description>&lt;p&gt;Let&amp;rsquo;s control volume with a (moderately) fancy knob!&lt;/p&gt;
&lt;div style="margin-left: auto; margin-right: auto;"&gt;
&lt;video controls="controls" style="width: 100%;"&gt;
&lt;source src="a72bc5_magknobVolume.mp4" type="video/mp4" /&gt;
&lt;/video&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://ams.com/as5601"&gt;AS5601&lt;/a&gt; might be my favourite sensor right now. It&amp;rsquo;s an on-axis magnetic
rotary position sensor. It generates a signal typical for encoders, but it does it by detecting the
change in a magnetic field of a diametrically magnetized, rotating magnet.&lt;/p&gt;

&lt;div id="__3d_scene" style="margin: auto; width: 100%;"&gt;
&lt;/div&gt;



&lt;p&gt;The knob acts as a USB HID device. That means that, currently, it can do everything that a 2 button
keyboard could do.&lt;/p&gt;</description><author>ciesie.com</author><pubDate>Sun, 13 Aug 2023 18:34:08 GMT</pubDate><guid isPermaLink="true">https://ciesie.com/project/magknob/</guid></item><item><title>Can an offshore dev agency steal your code?</title><link>https://vadimkravcenko.com/qa/can-offshore-dev-agency-steal-code/</link><description>&lt;p&gt;Question:&lt;/p&gt;
&lt;p&gt;hey Vadim, I'm a CTO at a newly created startup and we're looking to outsource development to an offshore dev agency. I understand the importance of the intellectual property. It's my first time working with an offshore outsourcing company, and I have some concerns about the security of our code, and who owns what. Specifically, I'm worried about the potential risk of our code being stolen or misused. Possible a clone being released? How real is this risk when working with offshore agencies? What measures can we take to protect our code and intellectual property? &lt;/p&gt;
&lt;p&gt; Answer:&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://vadimkravcenko.com/qa/can-offshore-dev-agency-steal-code/"&gt;Can an offshore dev agency steal your code?&lt;/a&gt; appeared first on &lt;a href="https://vadimkravcenko.com"&gt;Vadim Kravcenko&lt;/a&gt;.&lt;/p&gt;</description><author>Vadim Kravcenko</author><pubDate>Sun, 13 Aug 2023 18:20:29 GMT</pubDate><guid isPermaLink="true">https://vadimkravcenko.com/qa/can-offshore-dev-agency-steal-code/</guid></item><item><title>Is development the right career for me?</title><link>https://vadimkravcenko.com/qa/development-right-career/</link><description>&lt;p&gt;Question:&lt;/p&gt;
&lt;p&gt;Recently, during a work review with my boss, I was confronted with a question that's been lingering in my mind for a few years: "Is software development truly the right career for me?" While I've always delivered my tasks on time and met expectations, my boss noticed my lack of enthusiasm for programming outside of work and my indifference towards new technologies. Since my college days, I've felt somewhat ambivalent about computer science, even though I knew I wanted a career related to computers. While I've never been truly excited about diving deep into new programming languages or seeking out challenges for the sheer joy of it, I've always viewed my job as a means to an end - primarily, earning a paycheck. Now, I'm grappling with whether I should genuinely invest more passion into my career, seek out a role that simply requires me to code by specification, or consider an entirely different path altogether.&lt;/p&gt;
&lt;p&gt; Answer:&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://vadimkravcenko.com/qa/development-right-career/"&gt;Is development the right career for me?&lt;/a&gt; appeared first on &lt;a href="https://vadimkravcenko.com"&gt;Vadim Kravcenko&lt;/a&gt;.&lt;/p&gt;</description><author>Vadim Kravcenko</author><pubDate>Sun, 13 Aug 2023 16:41:53 GMT</pubDate><guid isPermaLink="true">https://vadimkravcenko.com/qa/development-right-career/</guid></item><item><title>Is coding bootcamp better than university?</title><link>https://vadimkravcenko.com/qa/coding-bootcamp-better-university/</link><description>&lt;p&gt;Question:&lt;/p&gt;
&lt;p&gt;Hi Vadim, I'm 20 and want to become a software engineer, I'm at a crossroads in deciding the best educational path to take. I've heard a lot about coding bootcamps from some of my friends and their promise of teaching practical skills from the get-go in 90 days. On the other hand, universities offer a more traditional and comprehensive education in computer science, but it's 4 years? I'm unsure which option would be more beneficial for someone like me, who's never done any programming in the past. Would a coding bootcamp help me get a high-paying job? Would it give me the necessary knowledge I need to jumpstart my career? Will it be enough? Or would you recommend going to a university?&lt;/p&gt;
&lt;p&gt; Answer:&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://vadimkravcenko.com/qa/coding-bootcamp-better-university/"&gt;Is coding bootcamp better than university?&lt;/a&gt; appeared first on &lt;a href="https://vadimkravcenko.com"&gt;Vadim Kravcenko&lt;/a&gt;.&lt;/p&gt;</description><author>Vadim Kravcenko</author><pubDate>Sun, 13 Aug 2023 16:41:24 GMT</pubDate><guid isPermaLink="true">https://vadimkravcenko.com/qa/coding-bootcamp-better-university/</guid></item><item><title>Deriving practices from principles</title><link>https://medium.com/@k0ryk/deriving-practices-from-principles-8350a6a27089?source=rss-3cf45d4f3676------2</link><description>&lt;h4&gt;Positive and negative ownership in software engineering&lt;/h4&gt;&lt;p&gt;The final exam for my AP Physics class in high school was one question. I had to derive E=MC² from Newton’s laws of motion. I felt very fortunate to have a class size of four people, with an educator who took his job seriously. He had individually tailored lessons for us, and when we weren’t being lectured, we had labs and tests. Those tests were the first time I ever remember experiencing &lt;a href="https://www.medicalnewstoday.com/articles/flow-state"&gt;flow state&lt;/a&gt;. I would look down at the paper, the time would melt away while I was singularly focused, immersed in the problem space.&lt;/p&gt;&lt;p&gt;Since those days, I have always found a deep satisfaction in deriving concepts. Derivation is like a compression algorithm for your memory — no need to memorize every equation when you can derive most from a few. I have sought the same sort of elegance outside of pure mathematics, but it doesn’t make sense for some things: I don’t have a grocery list that consists of the meals I am making this week. I could derive the ingredients I need while I am at the store, and have a much shorter list. It may work for talented chefs who know the laws of Cullinomancy, but I would just be memorizing ingredients, and might as well just write them down. I need axiomatic derivation that is accessible to me through understanding instead of pure memorization.&lt;/p&gt;&lt;p&gt;One unexpected place I think we can usefully apply derivation is in a much less mathematical world — the world of corporate guiding principals, more specifically how they apply software engineering. Here is an example; many guiding principles include something about risk taking, some are up front about it: “fail fast” or “embrace risk.” With others, the risk is implied by language encompassing speedy delivery, agility, high impact, etc. So the company wants to move fast and take risks, so how does that inform our Software Engineering practices?&lt;/p&gt;&lt;p&gt;With a principle like &lt;strong&gt;embrace risk&lt;/strong&gt; we can start deriving software engineering guidance from it. Embracing risk means being prepared for failure, and minimizing the impact. Some engineering principles that may arise from this focus on reducing risk and improving resiliency:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Pets vs. Cattle&lt;/li&gt;&lt;li&gt;Infrastructure as code&lt;/li&gt;&lt;li&gt;Database migrations&lt;/li&gt;&lt;li&gt;Automated testing / quality gates&lt;/li&gt;&lt;li&gt;Client, Server and DB rollback&lt;/li&gt;&lt;/ul&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ESfIEZFln_Nj2XbHEPHxVw.png" /&gt;&lt;figcaption&gt;I view culture as the sum of the individuals, it doesn’t exist outside of the people. Here, it represents the influence of their collective experience on practices. Source: DALL-E&lt;/figcaption&gt;&lt;/figure&gt;&lt;h4&gt;Vocabulary&lt;/h4&gt;&lt;p&gt;I think there is inconsistent vocabulary for words like principles, guidance, etc. Here are the definitions I am using:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;practices&lt;/strong&gt;: all the daily actions of individual contributors and teams. (e.g. code review, stand up, introducing bugs)&lt;br /&gt;&lt;strong&gt;principles&lt;/strong&gt;: the explicit set of principles provided by corporation (e.g. “embrace risk”).&lt;br /&gt;&lt;strong&gt;values&lt;/strong&gt;: the implicit set of principles within an organization &lt;br /&gt;&lt;strong&gt;guidance&lt;/strong&gt;: set of rules and practices provided by technical leadership in the organization.&lt;br /&gt;&lt;strong&gt;culture&lt;/strong&gt;: the sum of daily interactions between individuals, teams and their past professional and personal experience.&lt;/p&gt;&lt;p&gt;The iceberg analogy above is good because it indicates that what we mainly see are the practices, and we seldom inspect how they are influenced. It has a few assumptions baked in: the assumption that the company is effective at communicating their values and principles, and the assumption that ICs and leaders are able to effectively translate the values into practices. We also assume that there is only one set of principles. It may be the corporate-level guidance suggest speed and risk, while the guidance in the technology organization often focuses on governing risky engineering. What will a technical lead do when faced with the choice between delivering and following some technical guidance that may be cumbersome like 80% coverage of unit tests.&lt;/p&gt;&lt;h4&gt;When do practices matter?&lt;/h4&gt;&lt;p&gt;Every time someone makes a choice that effects the corporation’s creation of software. There is some duality to my definition of practice. Something becomes a practice by being done. Something is a practice if it has been done. In the act of creating practice, you are not following it. If this has a negative effect, it may be called &lt;strong&gt;bad practice.&lt;/strong&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ms5jhx7W8TMmkl6cOMSXdA.png" /&gt;&lt;figcaption&gt;Rendering of me deriving stuff. Source: DALL-E&lt;/figcaption&gt;&lt;/figure&gt;&lt;h4&gt;Deriving Software Engineering Goodness&lt;/h4&gt;&lt;p&gt;The values and principles of a company may be completely different, it’s a matter “what we say” vs. “what we do.” The practices followed by a software engineering org may not be consistent, they may not follow principles, guidance or values. However, when principles, values and guidance align it can have a powerful positive effect on practices.&lt;/p&gt;&lt;p&gt;The power comes with the ability to make a decision consistent with the principles of the company without needing permission or additional information. It’s empowering individuals to take ownership of the decision based on the compass they’re given. Some decisions should not be made in a vacuum, but teams should be empowered to make their own choices. Choices like who to hire, what processes to follow, what technology to use.&lt;/p&gt;&lt;p&gt;Many of the good practices become process, they may no longer seem like a choice because they have become so engrained in our daily structure. The same thing can happen with bad practices or lack of practices. Similarly, we can incorporate these choices into reusable technological components. Good practices can be made easy with smart choices baked into the libraries, modules and services we build or use. When properly aligned, our principles don’t just effect our every day choices, but also our technical road map. It effects the tools we build for ourselves, and in many ways it can become automated.&lt;/p&gt;&lt;h4&gt;Empowerment, Derivation and Network Effects&lt;/h4&gt;&lt;p&gt;One of the desired and intended effects of this approach is to have more decision making happening on the leaves of the org tree. You reduce overhead from communication/permission, empower your owners at the IC level, and leverage your culture of fail fast to know when to pivot. It all sounds great!&lt;/p&gt;&lt;p&gt;The one assumption about this happy path is the density of the organizational structure. Not the reporting hierarchy, but the communication graph. If we have a bunch of smart engineers all pondering the same problem, but they are not talking to each other. There is a large likelihood for overlap and therefore waste.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*p8vTxeASm8JM67J8JaxhvA.png" /&gt;&lt;figcaption&gt;Sparse organization example. Source: Me&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Introducing clear boundaries and responsibilities at the director and lead level can ensure that sparse communication organizations don’t create waste by doing duplicate work, or in the worst case: undoing each others’ work.&lt;/p&gt;&lt;h4&gt;Final Thoughts&lt;/h4&gt;&lt;p&gt;It is useful for technical leaders to provide guidance based on the corporation’s guiding principles. This allows individuals to derive correct and consistent practices, both empowering the individual and providing a more efficient outcome for the corporation. Achieving this requires alignment of principles, values and guidance. Misalignment can cause problems, and even perfect alignment can cause problems without effective communication. At the heart of these ideas is the relationship between the way the employees experience the organization (values and culture) and the way the organization presents themselves — their guiding principles. This is essentially a measure of corporate integrity, and low integrity will likely have negative effects regardless of the creation of software. Thank you for reading.&lt;/p&gt;&lt;img alt="" height="1" src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=8350a6a27089" width="1" /&gt;</description><author>Stories by Kory Kirk on Medium</author><pubDate>Sun, 13 Aug 2023 16:16:37 GMT</pubDate><guid isPermaLink="true">https://medium.com/@k0ryk/deriving-practices-from-principles-8350a6a27089?source=rss-3cf45d4f3676------2</guid></item><item><title>Why Blockbuster Was Right To Not Buy Netflix</title><link>https://matteoraso.github.io/why-blockbuster-was-right-to-not-buy-netflix.html</link><description>&lt;p&gt;Every now and then, you see people talk about how Blockbuster not buying Netflix was one of the worst financial decisions in history. That's arguable, but what people are usually trying to say is that Blockbuster made a &lt;em&gt;stupid&lt;/em&gt; decision, and anybody with a brain would have taken the deal …&lt;/p&gt;</description><author>Matteo's Website</author><pubDate>Sun, 13 Aug 2023 07:00:00 GMT</pubDate><guid isPermaLink="true">https://matteoraso.github.io/why-blockbuster-was-right-to-not-buy-netflix.html</guid></item><item><title>Behind 'Hello World' on Linux notes</title><link>https://blog.yelinaung.com/posts/behind-hello-world-on-linux-notes/</link><description>&lt;p&gt;I wrote these notes while reading Julia Evans&amp;rsquo; (excellent) &lt;a href="https://jvns.ca/blog/2023/08/03/behind--hello-world/"&gt;Behind &amp;ldquo;Hello World&amp;rdquo; on Linux post&lt;/a&gt; post.
I learned a lot from it.
If you haven&amp;rsquo;t read it yet, I highly recommend you do!&lt;/p&gt;
&lt;p&gt;I am on Ubuntu Linux 22.04 LTS and there is filed called &lt;code&gt;hello.py&lt;/code&gt; with content&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-python"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #cb4b16;"&gt;print&lt;/span&gt;(&lt;span style="color: #2aa198;"&gt;"Hello world!"&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s basically &amp;ldquo;What happens when you do &lt;code&gt;python hello.py&lt;/code&gt; in the terminal, on Linux ?&amp;rdquo; from the point you run the command to the point you see the output &amp;ldquo;Hello World!&amp;rdquo;.&lt;/p&gt;</description><author>Ye Lin's Random stuff</author><pubDate>Sun, 13 Aug 2023 05:21:14 GMT</pubDate><guid isPermaLink="true">https://blog.yelinaung.com/posts/behind-hello-world-on-linux-notes/</guid></item><item><title>Creating blurred background image using ImageMagick</title><link>https://arunmani.in/articles/creating-blurred-background-image/</link><description>An article on how to resize an image to an aspect ratio with its background blurred using ImageMagick.</description><author>Arun Mani</author><pubDate>Sun, 13 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://arunmani.in/articles/creating-blurred-background-image/</guid></item><item><title>(Not) fast dot products via SIMD</title><link>https://rachitsingh.com/notes/fma-dependency/</link><description>&lt;p&gt;Lately I've been tinkering with optimizing Sasha's &lt;a href="https://github.com/srush/llama2.rs"&gt;llama2.rs&lt;/a&gt;, a fast Rust port of Karpathy's llama2.c. It takes advantage of Rust nightly's &lt;code&gt;portable_simd&lt;/code&gt;, which allows you to emit AVX2 or AVX512 instructions using a relatively clean set of abstractions, and also run inference on a quantized llama2, so it's pretty fast.&lt;/p&gt;
&lt;p&gt;One of the core loops looks like this:&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #011627; color: #d6deeb;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; mask &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;= &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;1 &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;&amp;lt;&amp;lt; &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;BITS&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;- &lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;1&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span style="color: #637777;"&gt;// BITS is 4
&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; elems_per_i32 &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;= &lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;32 &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;/ &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;BITS&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; ipg: &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;usize &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;= &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;GROUPSIZE &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;/ &lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;32 &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;* &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;BITS&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span style="color: #637777;"&gt;// GROUPSIZE is 128, so this is 16 (left-associative)
&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; mask_4bits &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;= &lt;/span&gt;&lt;span&gt;i32x8&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;::&lt;/span&gt;&lt;span&gt;splat(mask);
&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; shift_right &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;= &lt;/span&gt;&lt;span&gt;i32x8&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;::&lt;/span&gt;&lt;span&gt;from_array([&lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;4&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;8&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;12&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;16&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;20&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;24&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;28&lt;/span&gt;&lt;span&gt;]);
&lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;...
&lt;/span&gt;&lt;span style="color: #637777;"&gt;// Do K at a time
&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; zero &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;= &lt;/span&gt;&lt;span&gt;f32x8&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;::&lt;/span&gt;&lt;span&gt;splat(&lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;0.0&lt;/span&gt;&lt;span&gt;);
&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; qzeros &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;= &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #addb67;"&gt;self&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span&gt;qzeros[oi &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;/&lt;/span&gt;&lt;span&gt; elems_per_i32]; &lt;/span&gt;&lt;span style="color: #637777;"&gt;// self is the neural net block this is part of
&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; out_elem &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;=&lt;/span&gt;&lt;span&gt; oi &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;%&lt;/span&gt;&lt;span&gt; elems_per_i32;
&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; qweight &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;= &lt;/span&gt;&lt;span style="color: #addb67;"&gt;self&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span&gt;qweight[oi]&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;chunks_exact&lt;/span&gt;&lt;span&gt;(ipg);
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; collect &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;= &lt;/span&gt;&lt;span style="color: #addb67;"&gt;self&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span&gt;scales[oi]
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;into_iter&lt;/span&gt;&lt;span&gt;()
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;zip&lt;/span&gt;&lt;span&gt;(qweight)
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;enumerate&lt;/span&gt;&lt;span&gt;()
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;map&lt;/span&gt;&lt;span&gt;(|(&lt;/span&gt;&lt;span style="color: #d7dbe0;"&gt;group&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span style="color: #d7dbe0;"&gt;scale&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d7dbe0;"&gt;qweight&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;| &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; qz &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;= &lt;/span&gt;&lt;span&gt;((qzeros[group] &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;BITS &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;*&lt;/span&gt;&lt;span&gt; out_elem)) &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; mask) &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;+ &lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;1&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; scale_simd &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;= &lt;/span&gt;&lt;span&gt;f32x8&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;::&lt;/span&gt;&lt;span&gt;splat(scale);
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; zero_simd &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;= &lt;/span&gt;&lt;span&gt;i32x8&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;::&lt;/span&gt;&lt;span&gt;splat(qz);
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; in_pos &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;=&lt;/span&gt;&lt;span&gt; group &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;* &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;GROUPSIZE&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; xs &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;=&lt;/span&gt;&lt;span&gt; x[in_pos&lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;..&lt;/span&gt;&lt;span&gt;in_pos &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;+ &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;GROUPSIZE&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;chunks_exact&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;8&lt;/span&gt;&lt;span&gt;);
&lt;/span&gt;&lt;span&gt;        qweight
&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;iter&lt;/span&gt;&lt;span&gt;()
&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;zip&lt;/span&gt;&lt;span&gt;(xs)
&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;map&lt;/span&gt;&lt;span&gt;(|(&lt;/span&gt;&lt;span style="color: #d7dbe0;"&gt;v&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d7dbe0;"&gt;x&lt;/span&gt;&lt;span&gt;)| {
&lt;/span&gt;&lt;span&gt;                &lt;/span&gt;&lt;span style="color: #637777;"&gt;//Extract v into 8 chunks
&lt;/span&gt;&lt;span&gt;                &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; x &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;= &lt;/span&gt;&lt;span&gt;f32x8&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;::&lt;/span&gt;&lt;span&gt;from_slice(x);
&lt;/span&gt;&lt;span&gt;                &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; num_simd &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;= &lt;/span&gt;&lt;span&gt;i32x8&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;::&lt;/span&gt;&lt;span&gt;splat(&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;*&lt;/span&gt;&lt;span&gt;v);
&lt;/span&gt;&lt;span&gt;                &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; qw: i32x8 &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;= &lt;/span&gt;&lt;span&gt;(num_simd &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; shift_right) &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; mask_4bits;
&lt;/span&gt;&lt;span&gt;                &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; combine: f32x8 &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;= &lt;/span&gt;&lt;span&gt;(qw &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;-&lt;/span&gt;&lt;span&gt; zero_simd)&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span&gt;cast&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;::&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;f32&lt;/span&gt;&lt;span&gt;&amp;gt;();
&lt;/span&gt;&lt;span&gt;                &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; weight: f32x8 &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;=&lt;/span&gt;&lt;span&gt; scale_simd &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;*&lt;/span&gt;&lt;span&gt; combine;
&lt;/span&gt;&lt;span&gt;                weight &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;*&lt;/span&gt;&lt;span&gt; x
&lt;/span&gt;&lt;span&gt;            })
&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;fold&lt;/span&gt;&lt;span&gt;(zero, |&lt;/span&gt;&lt;span style="color: #d7dbe0;"&gt;x&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d7dbe0;"&gt;y&lt;/span&gt;&lt;span&gt;| x &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;+&lt;/span&gt;&lt;span&gt; y)
&lt;/span&gt;&lt;span&gt;    })
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;fold&lt;/span&gt;&lt;span&gt;(zero, |&lt;/span&gt;&lt;span style="color: #d7dbe0;"&gt;x&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d7dbe0;"&gt;y&lt;/span&gt;&lt;span&gt;| x &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;+&lt;/span&gt;&lt;span&gt; y);
&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;*&lt;/span&gt;&lt;span&gt;o &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;=&lt;/span&gt;&lt;span&gt; collect&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;reduce_sum&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span style="color: #637777;"&gt;// output reference to write to
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, the &lt;code&gt;f32x8&lt;/code&gt; and &lt;code&gt;i32x8&lt;/code&gt; primitives are aliases for the &lt;code&gt;std::simd&lt;/code&gt; types that denote a SIMD vector with 8 lanes of &lt;code&gt;f32&lt;/code&gt; and &lt;code&gt;i32&lt;/code&gt; respectively (256 bits each). I have a 5700X, a Zen 3 CPU with &lt;code&gt;AVX2&lt;/code&gt; support but not &lt;code&gt;AVX512&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;When I profiled this, I saw that a lot of the time was being spent in the fold on the inside (i.e. the sum over results from mapping over &lt;code&gt;qweight&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Ok, so this is basically a SIMD dot-product between &lt;code&gt;x[in_pos..in_pos + GROUPSIZE]&lt;/code&gt; and whatever the &lt;code&gt;weight&lt;/code&gt; becomes. My limited SIMD experience is based on writing CUDA kernels, so naturally I thought that this might be better if I rewrote this to use the &lt;code&gt;fma&lt;/code&gt; instructions that have been around for the past few years.&lt;/p&gt;
&lt;p&gt;I rewrote the interior slightly:&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #011627; color: #d6deeb;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #637777;"&gt;// Do K at a time
&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; zero &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;= &lt;/span&gt;&lt;span&gt;f32x8&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;::&lt;/span&gt;&lt;span&gt;splat(&lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;0.0&lt;/span&gt;&lt;span&gt;);
&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; qzeros &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;= &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #addb67;"&gt;self&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span&gt;qzeros[oi &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;/&lt;/span&gt;&lt;span&gt; elems_per_i32];
&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; out_elem &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;=&lt;/span&gt;&lt;span&gt; oi &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;%&lt;/span&gt;&lt;span&gt; elems_per_i32;
&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; qweight &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;= &lt;/span&gt;&lt;span style="color: #addb67;"&gt;self&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span&gt;qweight[oi]&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;chunks_exact&lt;/span&gt;&lt;span&gt;(ipg);
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; collect &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;= &lt;/span&gt;&lt;span style="color: #addb67;"&gt;self&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span&gt;scales[oi]
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;into_iter&lt;/span&gt;&lt;span&gt;()
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;zip&lt;/span&gt;&lt;span&gt;(qweight)
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;enumerate&lt;/span&gt;&lt;span&gt;()
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;map&lt;/span&gt;&lt;span&gt;(|(&lt;/span&gt;&lt;span style="color: #d7dbe0;"&gt;group&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span style="color: #d7dbe0;"&gt;scale&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d7dbe0;"&gt;qweight&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;| &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; qz &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;= &lt;/span&gt;&lt;span&gt;((qzeros[group] &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;BITS &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;*&lt;/span&gt;&lt;span&gt; out_elem)) &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; mask) &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;+ &lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;1&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; scale_simd &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;= &lt;/span&gt;&lt;span&gt;f32x8&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;::&lt;/span&gt;&lt;span&gt;splat(scale);
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; qzero_simd &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;= &lt;/span&gt;&lt;span&gt;i32x8&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;::&lt;/span&gt;&lt;span&gt;splat(qz);
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; in_pos &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;=&lt;/span&gt;&lt;span&gt; group &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;* &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;GROUPSIZE&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; xs &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;=&lt;/span&gt;&lt;span&gt; x[in_pos&lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;..&lt;/span&gt;&lt;span&gt;in_pos &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;+ &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;GROUPSIZE&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;chunks_exact&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;8&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;map&lt;/span&gt;&lt;span&gt;(f32x8&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;::&lt;/span&gt;&lt;span&gt;from_slice);
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; q_op &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;=&lt;/span&gt;&lt;span&gt; qweight
&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;iter&lt;/span&gt;&lt;span&gt;()
&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;map&lt;/span&gt;&lt;span&gt;(|&lt;/span&gt;&lt;span style="color: #d7dbe0;"&gt;v&lt;/span&gt;&lt;span&gt;| {
&lt;/span&gt;&lt;span&gt;                &lt;/span&gt;&lt;span style="color: #637777;"&gt;//Extract v into 8 chunks
&lt;/span&gt;&lt;span&gt;                &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; num_simd &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;= &lt;/span&gt;&lt;span&gt;i32x8&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;::&lt;/span&gt;&lt;span&gt;splat(&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;*&lt;/span&gt;&lt;span&gt;v);
&lt;/span&gt;&lt;span&gt;                &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;let&lt;/span&gt;&lt;span&gt; qw: i32x8 &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;= &lt;/span&gt;&lt;span&gt;(num_simd &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; shift_right) &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; mask_4bits;
&lt;/span&gt;&lt;span&gt;                (qw &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;-&lt;/span&gt;&lt;span&gt; qzero_simd)&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span&gt;cast&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;::&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;f32&lt;/span&gt;&lt;span&gt;&amp;gt;()
&lt;/span&gt;&lt;span&gt;            });
&lt;/span&gt;&lt;span&gt;        xs&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;zip&lt;/span&gt;&lt;span&gt;(q_op)&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;fold&lt;/span&gt;&lt;span&gt;(zero, |&lt;/span&gt;&lt;span style="color: #d7dbe0;"&gt;acc&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span style="color: #d7dbe0;"&gt;x&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d7dbe0;"&gt;y&lt;/span&gt;&lt;span&gt;)| {
&lt;/span&gt;&lt;span&gt;            x&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;mul_add&lt;/span&gt;&lt;span&gt;(y, acc)
&lt;/span&gt;&lt;span&gt;        }) &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;*&lt;/span&gt;&lt;span&gt; scale_simd
&lt;/span&gt;&lt;span&gt;    })
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;fold&lt;/span&gt;&lt;span&gt;(zero, |&lt;/span&gt;&lt;span style="color: #d7dbe0;"&gt;x&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d7dbe0;"&gt;y&lt;/span&gt;&lt;span&gt;| x &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;+&lt;/span&gt;&lt;span&gt; y);
&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;*&lt;/span&gt;&lt;span&gt;o &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;=&lt;/span&gt;&lt;span&gt; collect&lt;/span&gt;&lt;span style="color: #c792ea;"&gt;.&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;reduce_sum&lt;/span&gt;&lt;span&gt;();
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, this tanked performance. I'm working on getting a proper hyperfine benchmark up and running&lt;sup class="footnote-reference" id="fr-1-1"&gt;&lt;a href="https://rachitsingh.com/notes/fma-dependency/#fn-1"&gt;1&lt;/a&gt;&lt;/sup&gt;, but it's safe to say that the performance is at least 50% worse. I thought issuing 1 instructon (&lt;code&gt;fma&lt;/code&gt;) per element of the vector (well, &lt;code&gt;N/8&lt;/code&gt; since I have 8 lanes) would be faster than issuing 2 instructions (&lt;code&gt;mul&lt;/code&gt; and &lt;code&gt;add&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The reason, as best as I can understand it (not SIMD expert, yet!) is that in the above code, we're bottlenecked on instruction &lt;em&gt;latency&lt;/em&gt;, not &lt;em&gt;throughput&lt;/em&gt;. Apparently the computation&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #011627; color: #d6deeb;"&gt;&lt;code class="language-python"&gt;&lt;span&gt;result &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;= &lt;/span&gt;&lt;span&gt;weight[&lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;0&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;* &lt;/span&gt;&lt;span&gt;x[&lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;0&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;+ &lt;/span&gt;&lt;span&gt;weight[&lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;1&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;* &lt;/span&gt;&lt;span&gt;x[&lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;1&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;+ &lt;/span&gt;&lt;span style="color: #ff5874;"&gt;... &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;+ &lt;/span&gt;&lt;span&gt;x[n] &lt;/span&gt;&lt;span style="color: #c792ea;"&gt;* &lt;/span&gt;&lt;span&gt;weight[n]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;can be run highly out-of-order because it doesn't matter which multiplication happens first, or technically in which order the results are added&lt;sup class="footnote-reference" id="fr-2-1"&gt;&lt;a href="https://rachitsingh.com/notes/fma-dependency/#fn-2"&gt;2&lt;/a&gt;&lt;/sup&gt;. That's why I see the &lt;code&gt;add&lt;/code&gt; in the profiling output, because that's the instruction that ends up being where the CPU is waiting for the result of all the individual multiplications to finish.&lt;/p&gt;
&lt;p&gt;In contrast:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #011627; color: #d6deeb;"&gt;&lt;code class="language-python"&gt;&lt;span&gt;result &lt;/span&gt;&lt;span style="color: #7fdbca;"&gt;= &lt;/span&gt;&lt;span style="color: #addb67;"&gt;fma&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;(&lt;/span&gt;&lt;span style="color: #addb67;"&gt;fma&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;(x[&lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;2&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;]&lt;/span&gt;&lt;span style="color: #d9f5dd;"&gt;, &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;weight[&lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;2&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;]&lt;/span&gt;&lt;span style="color: #d9f5dd;"&gt;, &lt;/span&gt;&lt;span style="color: #addb67;"&gt;fma&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;(x[&lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;1&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;]&lt;/span&gt;&lt;span style="color: #d9f5dd;"&gt;, &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;weight[&lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;1&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;]&lt;/span&gt;&lt;span style="color: #d9f5dd;"&gt;, &lt;/span&gt;&lt;span style="color: #addb67;"&gt;fma&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;(x[&lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;0&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;]&lt;/span&gt;&lt;span style="color: #d9f5dd;"&gt;, &lt;/span&gt;&lt;span style="color: #82aaff;"&gt;weight[&lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;0&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;])&lt;/span&gt;&lt;span style="color: #d9f5dd;"&gt;, &lt;/span&gt;&lt;span style="color: #f78c6c;"&gt;0&lt;/span&gt;&lt;span style="color: #82aaff;"&gt;)))
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;is highly order dependent, so the execution is much slower (also, I think in this case &lt;code&gt;fma&lt;/code&gt; latency is 4 cycles vs 3 cycles for &lt;code&gt;add&lt;/code&gt; and &lt;code&gt;mul&lt;/code&gt;, which makes things much slower). Compilers are pretty smart! And modern out-of-order execution is pretty impressive.&lt;/p&gt;
&lt;p&gt;Here's the people who helped me figure this out:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://stackoverflow.com/questions/72501374/is-vfmadd132pd-slow-on-amd-zen-3-architecture"&gt;Is vfmadd132pd slow on AMD Zen 3 architecture?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://stackoverflow.com/questions/63095394/latency-bounds-and-throughput-bounds-for-processors-for-operations-that-must-occ"&gt;Latency and throughput&lt;/a&gt;&lt;/p&gt;
&lt;footer class="footnotes"&gt;
&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-1"&gt;
&lt;p&gt;The full program is hard to benchmark because a ton of the initial time is spent mmaping the huge weights into memory, so I think it depends a lot on what the rest of my machine is doing (I think I have a fairly slow SSD at the moment). &lt;a href="https://rachitsingh.com/notes/fma-dependency/#fr-1-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-2"&gt;
&lt;p&gt;I'm not entirely sure about this, actually, since I think Rust doesn't enable &lt;code&gt;-ffast-math&lt;/code&gt; in release builds (I'm not sure how to, at the moment), so I think this operation still ends up being non-associative. &lt;a href="https://rachitsingh.com/notes/fma-dependency/#fr-2-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/footer&gt;</description><author>Rachit Singh</author><pubDate>Sun, 13 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://rachitsingh.com/notes/fma-dependency/</guid></item><item><title>Next.js Async Functions</title><link>https://www.danielcorin.com/til/nextjs/async-functions/</link><description>Next.js Async Functions</description><author>Thought Eddies</author><pubDate>Sun, 13 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/nextjs/async-functions/</guid></item><item><title>SIGGRAPH 2023 Conference Paper- Progressive Null-tracking for Volumetric Rendering</title><link>https://blog.yiningkarlli.com/2023/08/progressive-null-tracking.html</link><description>&lt;p&gt;This year at SIGGRAPH 2023, we have a conference-track technical paper in collaboration with Zackary Misso and Wojciech Jarosz from Dartmouth College!
The paper is titled “&lt;a href="http://dx.doi.org/10.1145/3588432.3591557"&gt;Progressive Null-tracking for Volumetric Rendering&lt;/a&gt;” and is the result of work that Zackary did while he was a summer intern with the Hyperion development team last summer.
On the Disney Animation side, Brent Burley, Dan Teece, and I oversaw Zack’s internship work, while on the the Dartmouth side, Wojciech was involved in the project as both Zack’s PhD advisor and as a consultant to Disney Animation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Aug/progressive-null-tracking/teaser.png"&gt;
&lt;img alt="Figure 1 from the paper: Most existing unbiased null-scattering methods for heterogeneous participating media require knowledge of a maximum density (majorant) to perform well. Unfortunately, bounding majorants are difficult to guarantee in production, and existing methods like ratio tracking and weighted delta tracking (top, left) suffer from extreme variance if the “majorant” (𝜇𝑡 =0.01) significantly underestimates the maximum density of the medium (𝜇𝑡 ≈3.0). Starting with the same poor estimate for a majorant (𝜇𝑡 = 0.01), we propose to instead clamp the medium density to the chosen majorant. This allows fast, low-variance rendering, but of a modified (biased) medium (top, center). We then show how to progressively update the majorant estimates (bottom row) to rapidly reduce this bias and ensure that the running average (top right) across multiple pixel samples converges to the correct result in the limit." src="https://blog.yiningkarlli.com/content/images/2023/Aug/progressive-null-tracking/preview/teaser.webp" /&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the paper abstract:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Null-collision approaches for estimating transmittance and sampling free-flight distances are the current state-of-the-art for unbiased rendering of general heterogeneous participating media. However, null-collision approaches have a strict requirement for specifying a tightly bounding total extinction in order to remain both robust and performant; in practice this requirement restricts the use of null-collision techniques to only participating media where the density of the medium at every possible point in space is known a-priori. In production rendering, a common case is a medium in which density is defined by a black-box procedural function for which a bounding extinction cannot be determined beforehand. Typically in this case, a bounding extinction must be approximated by using an overly loose and therefore computation- ally inefficient conservative estimate. We present an analysis of how null-collision techniques degrade when a more aggressive initial guess for a bounding extinction underestimates the true maximum density and turns out to be non-bounding. We then build upon this analysis to arrive at two new techniques: first, a practical, efficient, consistent progressive algorithm that allows us to robustly adapt null-collision techniques for use with procedural media with unknown bounding extinctions, and second, a new importance sampling technique that improves ratio-tracking based on zero-variance sampling.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The paper and related materials can be found at:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://www.yiningkarlli.com/projects/progressivenulltracking.html"&gt;Project Page (Author’s Version and Supplementary Material)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://cs.dartmouth.edu/~wjarosz/publications/misso23progressive.html"&gt;Wojciech Jarosz’s Project Page (Author’s Version and Supplementary Material)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="http://dx.doi.org/10.1145/3588432.3591557"&gt;Official Print Version (ACM Library)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One cool thing about this project is that this project both served as a direct extension of Zack’s PhD research area and served as a direct extension of the approach we’ve been taking to volume rendering in Disney’s Hyperion Renderer over the past 6 years.
Hyperion has always used unbiased transmittance estimators for volume rendering (as opposed to biased ray marching) &lt;a href="https://doi.org/10.1145/3084873.3084907"&gt;[Fong et al. 2017]&lt;/a&gt;, and Hyperion’s modern volume rendering system is heavily based on null-collision theory &lt;a href="https://www.yiningkarlli.com/projects/specdecomptracking/references/Woodcock1965.pdf"&gt;[Woodcock et al. 1965]&lt;/a&gt;.
We’ve put significant effort into making a null-collision based volume rendering system robust and practical in production, which led to projects such as residual ratio tracking &lt;a href="https://doi.org/10.1145/2661229.2661292"&gt;[Novák et al. 2014]&lt;/a&gt;, spectral and decomposition tracking &lt;a href="https://doi.org/10.1145/3072959.3073665"&gt;[Kutz et al. 2017]&lt;/a&gt; and approaches for unbiased emission and scattering importance sampling in heterogeneous volumes &lt;a href="https://doi.org/10.1145/3450623.3464644"&gt;[Huang et al. 2021]&lt;/a&gt;.
Over the past decade, many other production renderers &lt;a href="https://dl.acm.org/citation.cfm?id=3182162"&gt;[Christensen et al. 2018&lt;/a&gt;, &lt;a href="https://doi.org/10.1145/3214834.3214864"&gt;Gamito 2018&lt;/a&gt;, &lt;a href="https://doi.org/10.1111/cgf.13383"&gt;Novák et al. 2018]&lt;/a&gt; have similarly made the shift to null-collision based volume rendering because of the many benefits that the null-collision framework brings, such as unbiased volume rendering and efficient handling of volumes with lots of high-order scattering due to the null-collision framework’s ability to cheaply perform distance sampling.
Vanilla null-collision volume rendering does have shortcomings, such as difficulty in efficiently sampling optically thin volumes due to the fact that null-collision tracking techniques produce a binary transmittance estimate that is super noisy.
A lot of progress has been made in improving null-collision volume rendering’s efficiency and robustness in these thin volumes cases &lt;a href="http://jcgt.org/published/0002/02/10/"&gt;[Villemin and Hery 2013&lt;/a&gt;, &lt;a href="http://graphics.pixar.com/library/ThinMediaRendering/"&gt;Villemin et al. 2018&lt;/a&gt;, &lt;a href="https://dl.acm.org/citation.cfm?id=3230635"&gt;Herholz et al. 2019&lt;/a&gt;, &lt;a href="https://doi.org/10.1145/3306346.3323025"&gt;Miller et al. 2019]&lt;/a&gt;; the intro to the paper goes into much more extensive detail about these advancements.&lt;/p&gt;

&lt;p&gt;However, one major limitation of null-collision volume rendering that remained unsolved until this paper is that the null-collision framework requires knowing the maximum density, or &lt;em&gt;bounding majorant&lt;/em&gt; of a heterogeneous volume beforehand.
This is a fundamental requirement of null-collision volume rendering that makes using procedurally defined volumes difficult, since the maximum possible density value of a procedurally defined volume cannot be known a-priori without either putting into place a hard clamp or densely evaluating the procedural function.
As a result, renderers that use null-collision volume rendering typically only support procedurally defined volumes by pre-rasterizing the procedural function onto a fixed voxel grid, à la the volume pre-shading in Manuka &lt;a href="https://dl.acm.org/citation.cfm?id=3182161"&gt;[Fascione et al. 2018]&lt;/a&gt;. 
The need to pre-rasterize procedural volumes negates a lot of the workflow and artistic benefits of using procedural volumes; this is one of several reasons why other renderers continue to use ray-marching based integrators for volumes despite the bias and loss of efficiency at handling high-order scattering.
Inspired by ongoing challenges we were facing with rendering huge volume-scapes on &lt;em&gt;Strange World&lt;/em&gt; at the time, we gave Zack a very open-ended challenge for his internship: brainstorm and experiment with ways to lift this limitation in null-collision volume rendering.&lt;/p&gt;

&lt;p&gt;Zack’s PhD research coming into this internship revolved around deeply investigating the math behind modern volume rendering theory, and from these investigations, Zack had previously found deep new insights into how to formulate volumetric transmittance &lt;a href="https://doi.org/10.1145/3355089.3356559"&gt;[Georgiev et al. 2019]&lt;/a&gt; and cool new ways to de-bias previously biased techniques such as ray marching &lt;a href="https://doi.org/10.1145/3528223.3530160"&gt;[Misso et al. 2022]&lt;/a&gt;.
Zack’s solution to the procedural volumes in null-collision volume rendering problem very much follows in the same trend as his previous papers; after initially attempting to find ways to adapt de-biased ray marching to fit into a null-collision system, Zack went back to first principles and had the insight that a better solution was to find a way to de-bias the result that one gets from clamping the majorant of a procedural function.
This idea really surprised me when he first proposed it; I had never thought about the problem from this perspective before.
Dan, Brent, and I were highly impressed!&lt;/p&gt;

&lt;p&gt;In addition to the acknowledgements in the paper, I wanted to acknowledge here Henrik Falt and Jesse Erickson from Disney Animation, who spoke with Zack and us early in the project to help us better understand how better procedural volumes support in Hyperion could benefit FX artist workflows.
We are also very grateful to Disney Animation’s CTO, Nick Cannon, for granting us permission to include example code implemented in Mitsuba as part of the paper’s supplemental materials.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2023/Aug/progressive-null-tracking/procedural_bunny.png"&gt;&lt;img alt="One of my favorite images from this paper: a procedurally displaced volumetric Stanford bunny rendered using the progressive null tracking technique from the paper." src="https://blog.yiningkarlli.com/content/images/2023/Aug/progressive-null-tracking/procedural_bunny.png" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A bit of a postscript: during the Q&amp;amp;A session after Zack’s paper presentation at SIGGRAPH, Zack and I had a chat with Wenzel Jakob, Merlin Nimier-David, Delio Vicini, and Sébastien Speierer from &lt;a href="https://rgl.epfl.ch"&gt;EPFL’s Realistic Graphics Lab&lt;/a&gt;.
Wenzel’s group brought up a potential use case for this paper that we hadn’t originally thought of.
Neural radiance fields (NeRFs) &lt;a href="https://doi.org/10.1007/978-3-030-58452-8_24"&gt;[Mildenhall et al. 2020&lt;/a&gt;, &lt;a href="https://doi.org/10.1145/3587423.3595477"&gt;Takikawa et al. 2023]&lt;/a&gt; are typically rendered using ray marching, but this is often inefficient.
Rendering NeRFs using null tracking instead of ray marching is an interesting idea, but the neural networks that underpin NeRFs are essentially similar to procedural functions as far as null-collision tracking is concerned because there’s no way to know a tight bounding majorant for a neural network a-priori without densely evaluating the neural network.
Progressive null tracking solves this problem and potentially opens the door to more efficient and interesting new ways to render NeRFs!
If you happen to be interested in this problem, please feel free to reach out to Zack, Wojciech, and myself.&lt;/p&gt;

&lt;p&gt;Getting to work with Zack and Wojciech on this project was an honor and a blast; I count myself as very lucky that working at Disney Animation continues to allow me to meet and work with rendering folks from across our field!&lt;/p&gt;

&lt;h2 id="references"&gt;References&lt;/h2&gt;

&lt;p&gt;Brent Burley, David Adler, Matt Jen-Yuan Chiang, Hank Driskill, Ralf Habel, Patrick Kelly, Peter Kutz, Yining Karl Li, and Daniel Teece. 2018. &lt;a href="https://dl.acm.org/citation.cfm?id=3182159"&gt;The Design and Evolution of Disney’s Hyperion Renderer&lt;/a&gt;. &lt;em&gt;ACM Transactions on Graphics&lt;/em&gt; 37, 3 (Jul. 2018), Article 33.&lt;/p&gt;

&lt;p&gt;Per Christensen, Julian Fong, Jonathan Shade, Wayne Wooten, Brenden Schubert, Andrew Kensler, Stephen Friedman, Charlie Kilpatrick, Cliff Ramshaw, Marc Bannister, Brenton Rayner, Jonathan Brouillat, and Max Liani. 2018. &lt;a href="https://dl.acm.org/citation.cfm?id=3182162"&gt;RenderMan: An Advanced Path-Tracing Architeture for Movie Rendering&lt;/a&gt;. &lt;em&gt;ACM Transactions on Graphics&lt;/em&gt; 37, 3 (Jul. 2018), Article 30.&lt;/p&gt;

&lt;p&gt;Luca Fascione, Johannes Hanika, Mark Leone, Marc Droske, Jorge Schwarzhaupt, Tomáš Davidovič, Andrea Weidlich and Johannes Meng. 2018. &lt;a href="https://dl.acm.org/citation.cfm?id=3182161"&gt;Manuka: A Batch-Shading Architecture for Spectral Path Tracing in Movie Production&lt;/a&gt;. &lt;em&gt;ACM Transactions on Graphics&lt;/em&gt; 37, 3 (Jul. 2018), Article 31.&lt;/p&gt;

&lt;p&gt;Julian Fong, Magnus Wrenninge, Christopher Kulla, and Ralf Habel. 2017. &lt;a href="https://doi.org/10.1145/3084873.3084907"&gt;Production Volume Rendering&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2017 Courses&lt;/em&gt;. Article 2.&lt;/p&gt;

&lt;p&gt;Manuel Gamito. 2018. &lt;a href="https://doi.org/10.1145/3214834.3214864"&gt;Path Tracing the Framestorian Way&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2018 Course Notes: Path Tracing in Production&lt;/em&gt;. 52-61.&lt;/p&gt;

&lt;p&gt;Sebastian Herholz, Yangyang Zhao, Oskar Elek, Derek Nowrouzezahrai, Hendrik P A Lensch, and Jaroslav Křivánek. 2019. &lt;a href="https://dl.acm.org/citation.cfm?id=3230635"&gt;Volume Path Guiding Based on Zero-Variance Random Walk Theory&lt;/a&gt;. &lt;em&gt;ACM Transactions on Graphics&lt;/em&gt; 38, 3 (Jun. 2019), Article 25.&lt;/p&gt;

&lt;p&gt;Wei-Feng Wayne Huang, Peter Kutz, Yining Karl Li, and Matt Jen-Yuan Chiang. 2021. &lt;a href="https://dl.acm.org/doi/10.1145/3450623.3464644"&gt;Unbiased Emission and Scattering Importance Sampling For Heterogeneous Volumes&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2021 Talks&lt;/em&gt;. Article 3.&lt;/p&gt;

&lt;p&gt;Peter Kutz, Ralf Habel, Yining Karl Li, and Jan Novák. 2017. &lt;a href="https://doi.org/10.1145/3072959.3073665"&gt;Spectral and Decomposition Tracking for Rendering Heterogeneous Volumes&lt;/a&gt;. &lt;em&gt;ACM Transactions on Graphics (Proc. of SIGGRAPH)&lt;/em&gt; 36, 4 (Aug. 2017), Article 111.&lt;/p&gt;

&lt;p&gt;Ben Mildenhall, Pratul P. Srinivasan, Matthew Tancik, Jonathan T. Barron, Ravi Ramamoorthi, and Ren Ng. 2020. &lt;a href="https://doi.org/10.1007/978-3-030-58452-8_24"&gt;NeRF: Representing Scenes as Neural Radiance Fields for View Synthesis&lt;/a&gt;. In &lt;em&gt;Proc. of European Conference on Computer Vision (ECCV 2020)&lt;/em&gt;. 405-421.&lt;/p&gt;

&lt;p&gt;Bailey Miller, Iliyan Georgiev, and Wojciech Jarosz. 2019. &lt;a href="https://doi.org/10.1145/3306346.3323025"&gt;A Null-Scattering Path Integral Formulation of Light Transport&lt;/a&gt;. &lt;em&gt;ACM Transactions on Graphics (Proc. of SIGGRAPH)&lt;/em&gt; 38, 4 (Jul. 2019), Article 44.&lt;/p&gt;

&lt;p&gt;Jan Novák, Iliyan Georgiev, Johannes Hanika, and Wojciech Jarosz. 2018. &lt;a href="https://doi.org/10.1111/cgf.13383"&gt;Monte Carlo Methods for Volumetric Light Transport Simulation&lt;/a&gt;. &lt;em&gt;Computer Graphics Forum (Proc. of Eurographics)&lt;/em&gt; 37, 2 (May 2018), 551-576.&lt;/p&gt;

&lt;p&gt;Jan Novák, Andrew Selle and Wojciech Jarosz. 2014. &lt;a href="https://dl.acm.org/citation.cfm?id=2661292"&gt;Residual Ratio Tracking for Estimating Attenuation in Participating Media&lt;/a&gt;. &lt;em&gt;ACM Transactions on Graphics (Proc. of SIGGRAPH Asia)&lt;/em&gt; 33, 6 (Nov. 2014), Article 179.&lt;/p&gt;

&lt;p&gt;Towaki Takikawa, Shunsuke Saito, James Tompkin, Vincent Sitzmann, Srinath Sridhar, Or Litany, and Alex Yu. 2023. &lt;a href="https://doi.org/10.1145/3587423.3595477"&gt;Neural Fields for Visual Computing&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2023 Courses&lt;/em&gt;. Article 10.&lt;/p&gt;

&lt;p&gt;Ryusuke Villemin and Christophe Hery. 2013. &lt;a href="http://jcgt.org/published/0002/02/10/"&gt;Practical Illumination from Flames&lt;/a&gt;. &lt;em&gt;Journal of Computer Graphics Techniques&lt;/em&gt; 2, 2 (Dec. 2013), 142-155.&lt;/p&gt;

&lt;p&gt;Ryusuke Villemin, Magnus Wrenninge, and Julian Fong. 2018. &lt;a href="http://graphics.pixar.com/library/ThinMediaRendering/"&gt;Efficient Unbiased Rendering of Thin Participating Media&lt;/a&gt;. &lt;em&gt;Journal of Computer Graphics Techniques&lt;/em&gt; 7, 3 (Sep. 2018), 50-65.&lt;/p&gt;

&lt;p&gt;E. R. Woodcock, T. Murphy, P. J. Hemmings, and T. C. Longworth. 1965. &lt;a href="https://www.yiningkarlli.com/projects/specdecomptracking/references/Woodcock1965.pdf"&gt;Techniques used in the GEM code for Monte Carlo neutronics calculations in reactors and other systems of complex geometry&lt;/a&gt;. In &lt;em&gt;Applications of Computing Methods to Reactor Problems&lt;/em&gt;. Argonne National Laboratory.&lt;/p&gt;</description><author>Code &amp;amp; Visuals</author><pubDate>Sun, 13 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.yiningkarlli.com/2023/08/progressive-null-tracking.html</guid></item><item><title>Intro To Effect, Part 3: Managing Dependencies</title><link>https://ybogomolov.me/03-effect-managing-dependencies</link><description>&lt;p&gt;When we’re writing programs, we usually deal with some kind of dependencies: databases, loggers, caches, telemetry… Setting up a full-blown Dependency Injection framework could be a solution to this, but Effect has some aces in its sleeves that make DI frameworks unnecessary. Let’s see how we can manage dependencies using Effect.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;Intro to Effect series:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href="https://ybogomolov.me/01-effect-intro"&gt;What is Effect?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://ybogomolov.me/02-effect-handling-errors"&gt;Handling Errors&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Managing Dependencies&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://ybogomolov.me/04-effect-concurrency"&gt;Concurrency in Effect&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://ybogomolov.me/05-effect-stm"&gt;Software Transactional Memory in Effect&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;p&gt;Let’s recall the mental model of &lt;code class="language-plaintext highlighter-rouge"&gt;Effect&amp;lt;R, E, A&amp;gt;&lt;/code&gt;:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;E&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;E&lt;/span&gt; &lt;span class="err"&gt;⋃&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Today we’ll be talking about the &lt;code class="language-plaintext highlighter-rouge"&gt;(context: R) =&amp;gt;&lt;/code&gt; part. In programming, we seldom are working with completely pure functions, except, probably, when writing a mathematical library. In many cases, our code is working &lt;em&gt;in some kind of context&lt;/em&gt; — e.g., it has access to some environmental variables, can log something, can query a database or send something via a network protocol. But when we’re talking about functional programming, we want as many of our functions to be pure as possible. So how do we solve this conundrum?&lt;/p&gt;

&lt;p&gt;The answer is well-known for many decades — we make our pure functions take their dependencies as extra parameters. To make it simpler, the dependencies are usually put &lt;em&gt;before&lt;/em&gt; the actual arguments of the function:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;functionWithDeps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;endDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you have read my “Intro to fp-ts” series, you already recognised the &lt;code class="language-plaintext highlighter-rouge"&gt;Reader&lt;/code&gt; pattern. When we refactor this function definition a little, we get the following declaration:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Reader&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;functionWithDeps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;endDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Reader&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The same mental model also applies to Effect — of course, with a note that it isn’t implemented as an actual function, in contrast to &lt;code class="language-plaintext highlighter-rouge"&gt;Reader&lt;/code&gt; from &lt;code class="language-plaintext highlighter-rouge"&gt;fp-ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In Effect, we’re talking about &lt;em&gt;services&lt;/em&gt;. Each service is an entry in a &lt;em&gt;context&lt;/em&gt; represented by the &lt;code class="language-plaintext highlighter-rouge"&gt;R&lt;/code&gt; parameter of &lt;code class="language-plaintext highlighter-rouge"&gt;Effect&amp;lt;R, E, A&amp;gt;&lt;/code&gt; type. Let’s see how we can declare, use, and instantiate a service in Effect.&lt;/p&gt;

&lt;h2 id="declaration-of-a-service"&gt;Declaration Of A Service&lt;/h2&gt;

&lt;p&gt;To declare a service, you need to use the &lt;code class="language-plaintext highlighter-rouge"&gt;Context&lt;/code&gt; module. Each service is &lt;em&gt;tagged&lt;/em&gt; using a unique tag that helps differentiate different services on the type level:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Option&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;effect&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;DatabaseService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ConnectionError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DatabaseService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Tag&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DatabaseService&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// or you can provide a `key` to make this dependency global (singleton):&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DatabaseService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Tag&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DatabaseService&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DatabaseService&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It is recommended to name the tag of a service and its interface the same — in this case, you can import both things using the same name. Moreover, you should &lt;em&gt;think&lt;/em&gt; of them as a single type — just like &lt;code class="language-plaintext highlighter-rouge"&gt;zod&lt;/code&gt; validators and inferred types, or &lt;code class="language-plaintext highlighter-rouge"&gt;@effect/schema&lt;/code&gt; schemas and their inferred types. This may sound a bit controversial, but when you think about it a bit, it becomes logical and crystal clear. We already have one entity in the TypeScript type system that operates on both type and value levels — the &lt;code class="language-plaintext highlighter-rouge"&gt;class&lt;/code&gt; keyword. If you squint a bit, you’ll see that both schemas and their inferred types &lt;em&gt;are&lt;/em&gt; the same, so it is logical to name them the same. Similar reasoning applies to Effect services as well — a service interface and its &lt;code class="language-plaintext highlighter-rouge"&gt;tag&lt;/code&gt; should be seen as the same thing.&lt;/p&gt;

&lt;p&gt;You can think of &lt;code class="language-plaintext highlighter-rouge"&gt;Context&lt;/code&gt; as a hashmap of &lt;code class="language-plaintext highlighter-rouge"&gt;Tag -&amp;gt; Service implementation&lt;/code&gt;. When you access a service, you’re just adding a new entry to that hashmap — and when you finally want to interpret your expression, you need to provide implementations of all services.&lt;/p&gt;

&lt;h3 id="layers"&gt;Layers&lt;/h3&gt;

&lt;p&gt;Now, what if we want to create a service that is also dependent on another service? For example, a service for OpenSearch may be dependent on both an &lt;code class="language-plaintext highlighter-rouge"&gt;Http&lt;/code&gt; service and a &lt;code class="language-plaintext highlighter-rouge"&gt;Cache&lt;/code&gt; service, both of which depend on a &lt;code class="language-plaintext highlighter-rouge"&gt;Prometheus&lt;/code&gt; service to add some observability:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-mermaid"&gt;graph TD
  Base("Prometheus")
  S1("Http")
  S2("Cache")
  S3("OpenSearch")
  Base--&amp;gt;S1 &amp;amp; S2--&amp;gt;S3
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The Effect library has a recipe to solve this issue as well in the form of &lt;em&gt;layers&lt;/em&gt;. A &lt;code class="language-plaintext highlighter-rouge"&gt;Layer&amp;lt;In, E, Out&amp;gt;&lt;/code&gt; is a blueprint for building a &lt;code class="language-plaintext highlighter-rouge"&gt;Context&amp;lt;Out&amp;gt;&lt;/code&gt;. To create a layer with no dependencies, we use &lt;code class="language-plaintext highlighter-rouge"&gt;Layer.succeed&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: there is a convention taken from ZIO that production-ready layers have suffix &lt;code class="language-plaintext highlighter-rouge"&gt;Live&lt;/code&gt;, and all others (mostly, built for testing purposes) do not have such suffix.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Layer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;effect&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Prometheus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Prometheus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Tag&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Prometheus&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;myapp/prometheus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PrometheusLive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Layer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;succeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;Prometheus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Prometheus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="c1"&gt;// actual implementation is irrelevant here, so I'll just `succeed` it:&lt;/span&gt;
    &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;succeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// :: Layer&amp;lt;never, never, Prometheus&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, to create a layer that &lt;em&gt;depends&lt;/em&gt; on another layer, we use &lt;code class="language-plaintext highlighter-rouge"&gt;Layer.effect&lt;/code&gt;, because asking for a &lt;code class="language-plaintext highlighter-rouge"&gt;Tag&lt;/code&gt; in an effectful operation:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Http&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Tag&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HttpLive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Layer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;Http&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Prometheus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prometheus&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;Http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="c1"&gt;// exact implementation is also irrelevant here:&lt;/span&gt;
      &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;succeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;42&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;prometheus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// :: Layer&amp;lt;Prometheus, never, Http&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The same goes with the &lt;code class="language-plaintext highlighter-rouge"&gt;Cache&lt;/code&gt; layer — it is also dependent on &lt;code class="language-plaintext highlighter-rouge"&gt;Prometheus&lt;/code&gt;, so it will be created using the same template:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;CacheError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;_tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CacheError&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CacheError&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Cache&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CacheError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Tag&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;myapp/cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CacheLive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Layer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;Cache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Prometheus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;Cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;succeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;none&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;
      &lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;succeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// :: Layer&amp;lt;Prometheus, never, Cache&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note that I created &lt;code class="language-plaintext highlighter-rouge"&gt;Prometheus&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;Cache&lt;/code&gt; layers as singletons (global services), and &lt;code class="language-plaintext highlighter-rouge"&gt;Http&lt;/code&gt; is not global, because in general, we do not want to share the HTTP abstraction instances.&lt;/p&gt;

&lt;p&gt;To create our final layer, &lt;code class="language-plaintext highlighter-rouge"&gt;OpenSearch&lt;/code&gt;, we will just add another &lt;code class="language-plaintext highlighter-rouge"&gt;Tag&lt;/code&gt; into our effectful layer creation process:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;OpenSearch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ParseError&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;CacheError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;OpenSearch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Tag&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;OpenSearch&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;OpenSearchLive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Layer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;OpenSearch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;Http&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Cache&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;OpenSearch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;search&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maybeResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;maybeResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nx"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="na"&gt;onNone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
                  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;searchResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/search?q=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
                    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="nx"&gt;searchResult&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
                    &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                  &lt;span class="p"&gt;}),&lt;/span&gt;
                &lt;span class="na"&gt;onSome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;succeed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// :: Layer&amp;lt;Http | Cache, never, OpenSearch&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;I want to stop for a bit and talk about why &lt;code class="language-plaintext highlighter-rouge"&gt;Layer&lt;/code&gt; has a covariant &lt;code class="language-plaintext highlighter-rouge"&gt;In&lt;/code&gt; parameter (and, coincidentally, why &lt;code class="language-plaintext highlighter-rouge"&gt;Effect&lt;/code&gt; is covariant on its &lt;code class="language-plaintext highlighter-rouge"&gt;R&lt;/code&gt; param as well). This may feel illogical and just wrong, so here I’ll provide my own understanding of this whole situation — maybe, it will help the Effect model to click with you.&lt;/p&gt;

  &lt;p&gt;If you open &lt;a href="https://zio.dev"&gt;ZIO&lt;/a&gt; documentation, you’ll see that it is &lt;em&gt;contra&lt;/em&gt;variant on its &lt;code class="language-plaintext highlighter-rouge"&gt;R&lt;/code&gt; param, so combining &lt;code class="language-plaintext highlighter-rouge"&gt;ZIO[R1, E1, A]&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;ZIO[R2, E2, B]&lt;/code&gt; will yield &lt;code class="language-plaintext highlighter-rouge"&gt;ZIO[R1 &amp;amp; R2, E1 | E2, C]&lt;/code&gt;. For ZIO, a &lt;a href="https://ybogomolov.me/04-tasks#readertaskeitherr-e-a--a-task-which-runs-in-the-context-of-some-environment"&gt;ReaderTaskEither&lt;/a&gt; model works perfectly fine:&lt;/p&gt;
  &lt;div class="language-scala highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="kt"&gt;ZIO&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;R&lt;/span&gt;, &lt;span class="kt"&gt;E&lt;/span&gt;, &lt;span class="kt"&gt;A&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Either&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;E&lt;/span&gt;, &lt;span class="kt"&gt;A&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;p&gt;But this is not the case for Effect — its environmental parameters are &lt;em&gt;co&lt;/em&gt;variant, so combining &lt;code class="language-plaintext highlighter-rouge"&gt;Effect&amp;lt;R1, E1, A&amp;gt;&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;Effect&amp;lt;R2, E2, B&amp;gt;&lt;/code&gt;, you’ll get &lt;code class="language-plaintext highlighter-rouge"&gt;Effect&amp;lt;R1 | R2, E1 | E2, C&amp;gt;&lt;/code&gt;!&lt;/p&gt;

  &lt;p&gt;Motivation for this is quite pragmatic, but not that obvious: in TypeScript, it is relatively easy to exclude one member of a &lt;em&gt;union&lt;/em&gt; from it, but it is rather hard to exclude a part of &lt;em&gt;intersection&lt;/em&gt;.
That’s why in Effect, all context/environment parameters are &lt;em&gt;phantom&lt;/em&gt; and modelled using unions instead of intersections.&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;❗️ A rule of thumb&lt;/strong&gt;: when you look at &lt;code class="language-plaintext highlighter-rouge"&gt;R1 | R2&lt;/code&gt;, do not read it as “R1 or R2”, but rather as “R1 plus R2” (closer to type-theoretic meaning). So when you see an &lt;code class="language-plaintext highlighter-rouge"&gt;Effect&amp;lt;R1 | R2, E, A&amp;gt;&lt;/code&gt;, you should understand it as a computation that can fail with an error of type &lt;code class="language-plaintext highlighter-rouge"&gt;E&lt;/code&gt;, produce a value of type &lt;code class="language-plaintext highlighter-rouge"&gt;A&lt;/code&gt;, &lt;strong&gt;and requires both &lt;code class="language-plaintext highlighter-rouge"&gt;R1&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;R2&lt;/code&gt; (R1 + R2) dependencies present in the context&lt;/strong&gt; to be executed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, you might be confused with everything that’s going on here — services, layers, why do we need &lt;em&gt;that much&lt;/em&gt; abstraction just to work with dependencies? Hold tight, in a moment you’ll see the benefits this approach brings, but first let me show how to access services in your effectful code.&lt;/p&gt;

&lt;h2 id="using-services"&gt;Using Services&lt;/h2&gt;

&lt;p&gt;When you have access to service &lt;code class="language-plaintext highlighter-rouge"&gt;Tag&lt;/code&gt;, you can just &lt;em&gt;ask&lt;/em&gt; for a service instance, and you’ll get it in your pipeline:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;program&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;openSearch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OpenSearch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- accessing a service via Tag&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;foundUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;openSearch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="c1"&gt;//                         ^^^^^^^^^^^^^^^^^ using a service instance&lt;/span&gt;

  &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Found user: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;foundUser&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// :: Effect&amp;lt;OpenSearch, CacheError | ParseError, void&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Observe how bringing into program context and using a service automatically pulls its &lt;code class="language-plaintext highlighter-rouge"&gt;Context&lt;/code&gt; (&lt;code class="language-plaintext highlighter-rouge"&gt;R&lt;/code&gt;) and &lt;code class="language-plaintext highlighter-rouge"&gt;Error&lt;/code&gt; (&lt;code class="language-plaintext highlighter-rouge"&gt;E&lt;/code&gt;) types.&lt;/p&gt;

&lt;h2 id="providing-service-implementations"&gt;Providing Service Implementations&lt;/h2&gt;

&lt;p&gt;If you try running the &lt;code class="language-plaintext highlighter-rouge"&gt;program&lt;/code&gt; expression that we’ve built, you’ll find out that you can’t do this without providing a valid service implementation:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runPromise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;program&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;span class="c1"&gt;//                ^^^^^^^ Argument of type 'Effect&amp;lt;OpenSearch, never, void&amp;gt;' &lt;/span&gt;
&lt;span class="c1"&gt;//  is not assignable to parameter of type 'Effect&amp;lt;never, never, void&amp;gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We could do this using &lt;code class="language-plaintext highlighter-rouge"&gt;Effect.provideService&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;Effect.provideServiceEffect&lt;/code&gt; function, but that would be a very, VERY tedious and wordy process:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runPromise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;program&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provideServiceEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;OpenSearch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;Http&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Cache&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;OpenSearch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="c1"&gt;// the exact implementation we've used in OpenSearch layer&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provideServiceEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;Cache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Prometheus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;Cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="c1"&gt;// the exact implementation we've used in Cache layer&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provideServiceEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;Http&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Prometheus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prometheus&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;Http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="c1"&gt;// the exact implementation we've used in Http layer&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provideService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;Prometheus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;Prometheus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="c1"&gt;// the exact implementation we've used in Prometheus layer&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To make this more manageable, we will put those layers we’ve built to use.&lt;/p&gt;

&lt;h3 id="composing-layers"&gt;Composing Layers&lt;/h3&gt;

&lt;p&gt;The power of layers hides in their composition. Recall the service structure that we’ve built as layers:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-mermaid"&gt;graph TD
  Base("PrometheusLive :: Layer#lt;never, never, Prometheus#gt;")
  L1("HttpLive :: Layer#lt;Prometheus, never, Http#gt;")
  L2("CacheLive :: Layer#lt;Prometheus, never, Cache#gt;")
  L3("OpenSearchLive :: Layer#lt;Http | Cache, never, OpenSearch#gt;")

  Base--&amp;gt;L1 &amp;amp; L2--&amp;gt;L3
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To resolve this hierarchy into a single “provideable” layer, we need to compose them &lt;em&gt;vertically&lt;/em&gt; and &lt;em&gt;horizontally&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Let’s start with vertical composition. To satisfy the requirements of &lt;code class="language-plaintext highlighter-rouge"&gt;Http&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;Cache&lt;/code&gt; layers, we can use &lt;code class="language-plaintext highlighter-rouge"&gt;Layer.provide&lt;/code&gt; to build our composition:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HttpResolvedLive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;PrometheusLive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;Layer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;HttpLive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// :: Layer&amp;lt;never, never, Http&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CacheResolvedLive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;PrometheusLive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;Layer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CacheLive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// :: Layer&amp;lt;never, never, Cache&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Observe that after such composition we have eliminated &lt;code class="language-plaintext highlighter-rouge"&gt;Prometheus&lt;/code&gt; from dependencies of both &lt;code class="language-plaintext highlighter-rouge"&gt;HttpLive&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;CacheLive&lt;/code&gt; layers. Now we can use those layers to gradually resolve dependencies of &lt;code class="language-plaintext highlighter-rouge"&gt;OpenSearchLive&lt;/code&gt;:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;OpenSearchPartiallyResolvedLive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;HttpResolvedLive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;Layer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OpenSearchLive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// :: Layer&amp;lt;Cache, never, OpenSearch&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MainLive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CacheResolvedLive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;Layer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OpenSearchPartiallyResolvedLive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// :: Layer&amp;lt;never, never, OpenSearch&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That was a very inefficient way of building layers, I should say 😉 Thankfully, we have also a &lt;em&gt;horizontal&lt;/em&gt; composition of layers using &lt;code class="language-plaintext highlighter-rouge"&gt;Layer.merge&lt;/code&gt; function that allows us to build the resulting &lt;code class="language-plaintext highlighter-rouge"&gt;MailLive&lt;/code&gt; layer simply and elegantly:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HttpAndCacheLive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Layer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CacheLive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;HttpLive&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// :: Layer&amp;lt;Prometheus, never, Http | Cache&amp;gt;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MainLive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;PrometheusLive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;Layer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;HttpAndCacheLive&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// :: Layer&amp;lt;never, never, Http | Cache&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;Layer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OpenSearchLive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// :: Layer&amp;lt;never, never, OpenSearch&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This could be visualised as:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-mermaid"&gt;flowchart TD
  subgraph Before ["Initial layer hierarchy"]
    direction TB
    Base("PrometheusLive :: Layer#lt;never, never, Prometheus#gt;")
    L1("HttpLive :: Layer#lt;Prometheus, never, Http#gt;")
    L2("CacheLive :: Layer#lt;Prometheus, never, Cache#gt;")
    L3("OpenSearchLive :: Layer#lt;Http | Cache, never, OpenSearch#gt;")

    Base--&amp;gt;L1 &amp;amp; L2--&amp;gt;L3
  end

  subgraph H1 ["Horizontal composition"]
    direction TB
    H1Base("PrometheusLive :: Layer#lt;never, never, Prometheus#gt;")
    H1L12("HttpCacheLive :: Layer#lt;Prometheus, never, Http | Cache#gt;")
    H1L3("OpenSearchLive :: Layer#lt;Http | Cache, never, OpenSearch#gt;")

    H1Base--&amp;gt;H1L12
    H1L12--&amp;gt;H1L3
  end

  subgraph V1 ["Vertical composition"]
    direction TB
    V1L12("PrometheusHttpCacheLive :: Layer#lt;never, never, Http | Cache#gt;")
    V1L3("OpenSearchLive :: Layer#lt;Http | Cache, never, OpenSearch#gt;")

    V1L12--&amp;gt;V1L3
  end

  subgraph Final ["Final vertical composition"]
    direction TB
    FinalLayer("OpenSearchLive :: Layer#lt;never, never, OpenSearch#gt;")

    FinalLayer
  end

  Before --&amp;gt; H1
  H1 --&amp;gt; V1
  V1 --&amp;gt; Final
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now, we can provide our &lt;code class="language-plaintext highlighter-rouge"&gt;MainLive&lt;/code&gt; layer to our &lt;code class="language-plaintext highlighter-rouge"&gt;program&lt;/code&gt; and finally run it:&lt;/p&gt;

&lt;div class="language-ts highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runPromise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provideLayer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;program&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MainLive&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// :: Promise&amp;lt;void&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id="conclusion"&gt;Conclusion&lt;/h1&gt;

&lt;p&gt;At first, thinking about services and layers might be a bit overwhelming. I find it helpful to visualise my dependency graphs to understand better how my programs should be built. But in the end, I think the concept of layers is very useful, as it helps isolate core business logic from service implementation and it sets up a pretty decent dependency injection approach without all those implicit factories, registries, and service locators.&lt;/p&gt;

&lt;p&gt;If this analogy helps, you can think of composing layers as &lt;a href="https://en.wikipedia.org/wiki/Arrow_%28computer_science%29"&gt;Arrow-based programming&lt;/a&gt; — e.g., if you have worked with &lt;a href="https://rxjs.dev"&gt;RxJs&lt;/a&gt;, you already have experience composing arrows.&lt;/p&gt;</description><author/><pubDate>Sun, 13 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ybogomolov.me/03-effect-managing-dependencies</guid></item><item><title>Don't delegate understanding</title><link>https://stephango.com/understand</link><description>&lt;p&gt;There is a parasite, I see it everywhere. It consumes your health and wealth. It preys on ignorance and is easy to catch. It’s so common you may not even notice you have it.&lt;/p&gt;

&lt;p&gt;The parasite has a simple and attractive proposition: let me take care of this hard thing for you. Trust me, I know better.&lt;/p&gt;

&lt;p&gt;Instead of understanding it yourself, you choose to give the parasite control over your health, education, money, housing, business, identity, data, infrastructure, climate, justice. Even your beliefs.&lt;/p&gt;

&lt;p&gt;The parasite has three stages: acceptance, extraction, intervention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First is acceptance&lt;/strong&gt;. Everyone else seems to have the parasite already. You are expected, even encouraged, to accept the parasite into your life. You are invited to follow the norm, outsource, consume. It’s okay! Use all the services and amenities. Satisfy your desires. Eat the cheap food, watch the cheap media. Your money and time are meant to be spent. Show off what you got in exchange. Please do not try to understand how it works, it’s too complicated for you. The parasite wants you fattened. Literally and figuratively. You are paying the parasite for the privilege of being ripened.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second is extraction&lt;/strong&gt;. Under the influence of the parasite, you have developed unhealthy habits and you are suffering the consequences. Stress, anxiety, obesity, disease, fear, lethargy, decay. To dampen these problems you pay the parasite for help — support, medicine, loans, fines, rent, taxes. Enforcement of some homeostasis. You try to abate the issues, but you don’t have a stable foundation to build on. You have ignored the root causes. The parasite thrives. You are paying the parasite to be harvested, milked, sucked dry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third is intervention&lt;/strong&gt;. The side effects of the parasite’s extraction have reached a critical level. The parasite tells you it’s an emergency. You need doctors, lawyers, firefighters, a military effort. You’re in a surgery room, a court room, a psychiatric ward, a jail cell. The disease can no longer be controlled, it has festered. The flame has turned into a raging fire that needs to be put out. You are paying the parasite to go back to square one.&lt;/p&gt;

&lt;p&gt;The three stages of the parasite are interdependent. Every stage benefits someone who is not you. Everyone tells you this is just the way it is. Never mind that the parasite is living large.&lt;/p&gt;

&lt;p&gt;Why? Extraction and intervention pay well. Education and prevention do not. The incentives are aligned to make the parasite persuasive. You are alone against a coordinated system that is exceedingly effective at packaging problems you should never have with solutions you should never need. A symbiotic loop.&lt;/p&gt;

&lt;p&gt;You must recognize the parasite in its earliest form.&lt;/p&gt;

&lt;p&gt;To inoculate yourself don’t delegate understanding. If you build your own understanding you will be the one who earns the dividends.&lt;/p&gt;</description><author>Steph Ango</author><pubDate>Sun, 13 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://stephango.com/understand</guid></item><item><title>Ozymandias</title><link>https://blog.vslira.net/2023/08/ozymandias.html</link><description>&lt;blockquote&gt;Look on my works, ye Mighty, and despair!&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;The common interpretation of &lt;a href="https://en.wikipedia.org/wiki/Ozymandias#Text"&gt;the poem&lt;/a&gt; is that we're supposed to appreciate the irony of the self-styled king of kings' works not being around anymore. Time, like sand, eventually wears everything out. But I always found it funny that the pharaoh is addressing "ye Mighty,". Is it out of respect or irony that Ramesses II addresses his non-peers this way? The pharaoh not-so gently cues them to despair, so I don't suppose it's out of respect. But the irony is also a bit uncalled for: he already drove the point home about who's in charge with his title. Why "ye Mighty," then?&lt;br /&gt;&lt;br /&gt;Unless it's not supposed to be ironic, but earnest. Maybe Shelley isn't picturing a cocky, hubristic ruler, but a wise one. He was, after all, one of the most successful pharaohs of Egypt, in both war and diplomacy. Maybe the pharaoh is trying to send an important message to those who happen upon his works: "It doesn't matter how 'Mighty' you are. I was the greatest of all, and now, look around: all is for naught. Your work, too, will eventually be forgotten." This reflection on the transient nature of power and achievement, to me, is a more profound reason for "ye Mighty" to despair than simply observing the magnitude of his monuments when they were around.</description><author>vslira's blog</author><pubDate>Sun, 13 Aug 2023 02:38:19 GMT</pubDate><guid isPermaLink="true">https://blog.vslira.net/2023/08/ozymandias.html</guid></item><item><title>Free Interior Design With Sweet Home 3D and SketchUp</title><link>https://svedic.org/personal/free-interior-design-with-sweet-home-3d-and-sketchup</link><description>If you need a do-it-yourself interior design for your flat, house, basement, or dictator mansion—with completely free software—you are at the right place. Below is my experience with choosing software, finding 3D furniture models, finding textures, rendering, and furnishing the &amp;#8230; &lt;a href="https://svedic.org/personal/free-interior-design-with-sweet-home-3d-and-sketchup"&gt;Continue reading &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;</description><author>Svedic.org</author><pubDate>Sat, 12 Aug 2023 19:39:26 GMT</pubDate><guid isPermaLink="true">https://svedic.org/personal/free-interior-design-with-sweet-home-3d-and-sketchup</guid></item><item><title>Thoughts 7 — Microblogging, Solarpunk, AI and success</title><link>https://blog.separateconcerns.com/2023-08-12-thoughts-7.html</link><description>&lt;blockquote&gt;
&lt;p&gt;“Thoughts” are posts about what has been on my mind. Sometimes practical, sometimes not; often just things I read recently. Less thought out than regular posts.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;section id="Microblogging"&gt;
&lt;h2&gt;Microblogging&lt;/h2&gt;
&lt;p&gt;First a small announcement: &lt;a href="https://bsky.app/profile/catwell.info"&gt;I have joined Bluesky&lt;/a&gt;. I like it so far and I think it has a good founding team, so I want to believe in its long-term potential. On the other hand, I will likely stop using Mastodon soon; it is not working for me. I will stay on X for now, but I can see Bluesky replacing it entirely after a while.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="The-future-is-Solarpunk"&gt;
&lt;h2&gt;&lt;a href="https://feldspaten.org/2023/07/17/The-future-is-solarpunk/"&gt;The future is Solarpunk&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Let’s acknowledge the difficulties of our times but without accepting defeat in the process of trying to solve those issues. […] We are at the point where we can decide if we want to go full Cyperpunk or something more positive. Solarpunk is the movement, the idea behind envisioning and then creating a better future based on humans which live in harmony with nature.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If there is one thing I am certain of regarding climate change, it is that no amount of moderation will be enough to solve it without terrible consequences on a planetary scale. But there most likely won’t be a magical technological breakthrough either. We need to seriously start looking at technologies and ways of life that are both sustainable and acceptable for the whole planet.&lt;/p&gt;
&lt;p&gt;On a side note, I also think important technological breakthroughs are not easily predictable in detail. For instance, I think it is highly likely that AI will play a role in fighting climate change,
but I currently have no idea how.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="StochasticDeterministic"&gt;
&lt;h2&gt;&lt;a href="https://matt-rickard.com/stochastic-deterministic"&gt;Stochastic/Deterministic&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Generative AI introduces much more stochasticity into programming. […] Ironically, this makes the deterministic parts that much more important.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One of the first things I did at Finegrain was making sure I knew about all the sources of randomness, and then adding deterministic end-to-end tests.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="MiniGPT-4"&gt;
&lt;h2&gt;&lt;a href="https://minigpt-4.github.io"&gt;MiniGPT-4&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The authors have taken the visual understanding part of &lt;a href="https://arxiv.org/abs/2301.12597"&gt;BLIP-2&lt;/a&gt; (a ViT and a Querying Transformer) and aligned it with &lt;a href="https://lmsys.org/blog/2023-03-30-vicuna/"&gt;Vicuna&lt;/a&gt;. They achieved that by connecting them through a single 5120x768 linear layer. This illustrates well the unintuitive effectiveness of simple linear projections in high-dimensional spaces.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="What-took-you-here-won't-take-you-there"&gt;
&lt;h2&gt;What took you here won’t take you there&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;It is hard to grasp for some engineers early in their careers what took them here won’t take them there. Shipping features will probably take you to senior, shipping 10x more features is not how you’ll become a principal. What will get you to principal is removing chaos, unblocking others, fighting for things to be built in the right order, fighting scope creep, having backbone while knowing when to delegate decision making.&lt;/p&gt;
&lt;p&gt;– &lt;a href="https://twitter.com/rakyll/status/1666592449207336960"&gt;Jaana Dogan (@rakyll) on Twitter^WX&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/section&gt;
&lt;section id="Why-it's-necessary-to-shoot-yourself-in-the-foot"&gt;
&lt;h2&gt;&lt;a href="https://jacobgw.com/blog/observation/2023/07/08/shoot-yourself-in-the-foot.html"&gt;Why it’s necessary to shoot yourself in the foot&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;To fully understand a “best practice” or why something is necessary, it’s important to experience how things go wrong without it. When teaching programming, we should let people make these mistakes, and [only] then show them the tools to correct them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/section&gt;
&lt;section id="The-Happiness-Advantage"&gt;
&lt;h2&gt;The Happiness Advantage&lt;/h2&gt;
&lt;p&gt;There is &lt;a href="https://www.shawnachor.com/books/happiness-advantage/"&gt;a whole book&lt;/a&gt; dedicated to the fact that happiness fuels success and (mostly) not the other way around, and how to take advantage of it.&lt;/p&gt;
&lt;p&gt;I haven’t read the book (yet) but I have come to that conclusion independently for a while now. I found the book through &lt;a href="https://engineering.prezi.com/shifting-to-the-right-place-my-transition-back-from-em-to-engineer-d435acbabf2f"&gt;this post&lt;/a&gt; which is one of several about people moving back from management to engineering.&lt;/p&gt;
&lt;/section&gt;</description><author>Separate Concerns</author><pubDate>Sat, 12 Aug 2023 17:30:00 GMT</pubDate><guid isPermaLink="true">https://blog.separateconcerns.com/2023-08-12-thoughts-7.html</guid></item><item><title>Automatically Caption Your Videos with Whisper and ffmpeg</title><link>https://williamhuster.com/automatically-subtitle-videos/</link><description>&lt;p&gt;Big video handlers like YouTube and even Slack use AI to automatically caption videos that you upload. This is a great win for accessibility, and the AI is extremely accurate. It sometimes stumbles on uncommon words and bad audio signals, but these can be easily fixed by a human. I think most would agree that the benefit and cost savings of generating 95% accurate captions greatly outweighs the cost of having none at all!&lt;/p&gt;

&lt;p&gt;So at work we were talking about how we could build this feature ourselves using open source tools. I came up with this suggestion that uses &lt;a href="https://github.com/openai/whisper"&gt;OpenAI’s Whisper&lt;/a&gt; and the venerable &lt;a href="https://www.ffmpeg.org/"&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;ffmpeg&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are two steps to this (1) generate captions from the video as an SRT file, and (2) bundle the captions with the video.&lt;/p&gt;

&lt;p&gt;Whisper in my experience is incredibly good at audio transcription. And it can directly transcribe mp4 files to SRT format. You don’t have to extract the audio from the video or anything like that. Once you have an SRT file, &lt;code class="language-plaintext highlighter-rouge"&gt;ffmpeg&lt;/code&gt; can easily bundle an mp4 and SRT file. I’ve tried this on some personal videos, and the results have been near perfect.&lt;/p&gt;

&lt;h2 id="installation"&gt;Installation&lt;/h2&gt;

&lt;p&gt;Here is my project directory on GitHub. The README includes installation instructions, reproduced here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/whusterj/whisper-transcribe"&gt;https://github.com/whusterj/whisper-transcribe&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The only two hard dependencies are the &lt;code class="language-plaintext highlighter-rouge"&gt;ffmpeg&lt;/code&gt; system package and the &lt;code class="language-plaintext highlighter-rouge"&gt;openai-whisper&lt;/code&gt; Python package.&lt;/p&gt;

&lt;p&gt;This installation has been tested with &lt;code class="language-plaintext highlighter-rouge"&gt;Python 3.10.12&lt;/code&gt; on Ubuntu 20.02. It should work on other Platforms as well, and OpenAI says Whisper should work with all Python versions 3.9 to 3.11.&lt;/p&gt;

&lt;p&gt;First, you will need &lt;code class="language-plaintext highlighter-rouge"&gt;ffmpeg&lt;/code&gt; on your system, if you don’t have it already:&lt;/p&gt;

&lt;div class="language-bash highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="c"&gt;# on Ubuntu or Debian&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;ffmpeg

&lt;span class="c"&gt;# on MacOS using Homebrew (https://brew.sh/)&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;ffmpeg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For other platforms, see the [Whisper GitHub repo][1].&lt;/p&gt;

&lt;p&gt;Now you can install the python requirements. Create a virtual environment, activate it, and pip install from requirements.txt:&lt;/p&gt;

&lt;div class="language-bash highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv/
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you encounter errors during installation, you may need to install &lt;code class="language-plaintext highlighter-rouge"&gt;rust&lt;/code&gt; on your platform as well. If my requirements.txt fails to install for some reason, try just installing the &lt;code class="language-plaintext highlighter-rouge"&gt;openai-whisper&lt;/code&gt; package - this should install its Python sub-dependencies:&lt;/p&gt;

&lt;div class="language-bash highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;openai-whisper
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id="how-to-do-it"&gt;How to Do It&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Use &lt;code class="language-plaintext highlighter-rouge"&gt;whisper&lt;/code&gt; to generate an SRT transcription of the video:&lt;/p&gt;

&lt;div class="language-bash highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;whisper infile.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--model&lt;/span&gt; small.en &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--language&lt;/span&gt; English &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s1"&gt;'srt'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I tested this on a two minute video and it took less than 30 seconds to produce a complete transcription. This output is a subtitle file for your video - an SRT file. SRT is a plaintext file format, so you can edit it with your word processor of choice. Open the SRT, and you will see that each line of the file contains start and end timecodes and the caption text. This can be interpreted by video players to display the caption at the right time in sync with the video.&lt;/p&gt;

&lt;p&gt;I used the &lt;code class="language-plaintext highlighter-rouge"&gt;small.en&lt;/code&gt; language model, which is about 400MB to download. It may be less accurate than the larger models, but in my experience it does a really good job. The option &lt;code class="language-plaintext highlighter-rouge"&gt;-f 'srt'&lt;/code&gt; specifies that you want an SRT file, but you have your choice of text output format.&lt;/p&gt;

&lt;p&gt;You may want to quickly review the SRT file for any misinterpreted words or names&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Use &lt;code class="language-plaintext highlighter-rouge"&gt;ffmpeg&lt;/code&gt; to add the SRT as a subtitle track:&lt;/p&gt;

&lt;div class="language-bash highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; infile.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
       &lt;span class="nt"&gt;-i&lt;/span&gt; infile.srt &lt;span class="se"&gt;\&lt;/span&gt;
       &lt;span class="nt"&gt;-c&lt;/span&gt; copy &lt;span class="nt"&gt;-c&lt;/span&gt;:s mov_text &lt;span class="se"&gt;\&lt;/span&gt;
       outfile.mp4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This took less than 2 seconds to run. It doesn’t have to re-render the video, it just bundles the SRT alongside the video track inside of the mp4 container.&lt;/p&gt;

&lt;p&gt;You will need a player like VLC that can show you the subtitle tracks. It is also possible to use ffmpeg to “burn in” the subtitles from your SRT file, but this requires re-rendering the whole video.&lt;/p&gt;

&lt;h2 id="automate-it-all"&gt;Automate It All!&lt;/h2&gt;

&lt;p&gt;The above would be the “by hand” procedure, but perhaps you can see how easily this process might be automated in bulk.&lt;/p&gt;

&lt;p&gt;&lt;img alt="Automatic Video Captioning Pipeline Sketch" src="https://williamhuster.com/static/images/posts/2023-08-12-closed-captioning-data-pipeline-sketch.jpg" /&gt;
The idea would be that you can drop the mp4 files you want subtitled into an S3 bucket. Then you’d have a scheduler script detect them and queue up jobs for one or more workers. This isn’t strictly necessary, but would allow you to fan out the process to as many workers as you like to process more videos faster. The jobs would run the above commands and produce captioned video files automatically, which are then saved back to S3 somewhere.&lt;/p&gt;

&lt;h2 id="show-captions-in-html-without-embedding"&gt;Show Captions in HTML without Embedding&lt;/h2&gt;

&lt;p&gt;If you’re captioning videos for viewing in web browsers, there’s an even easier way.&lt;/p&gt;

&lt;p&gt;After my initial write-up, I learned that it’s also possible to attach the track to the video using HTML5’s built-in &lt;code class="language-plaintext highlighter-rouge"&gt;&amp;lt;video&amp;gt;&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;&amp;lt;track&amp;gt;&lt;/code&gt; tags. This cuts out step two above, meaning you won’t have to use ffmpeg to embed the track in the video file.&lt;/p&gt;

&lt;p&gt;In this case, you’ll want to generate a &lt;code class="language-plaintext highlighter-rouge"&gt;.vtt&lt;/code&gt; file instead of the &lt;code class="language-plaintext highlighter-rouge"&gt;.srt&lt;/code&gt; above. Fortunately, Whisper supports this format, too!&lt;/p&gt;

&lt;div class="language-bash highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;whisper infile.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--model&lt;/span&gt; small.en &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--language&lt;/span&gt; English &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s1"&gt;'vtt'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once you’ve generated your captions, you can use them with the HTML5 video player like this:&lt;/p&gt;

&lt;div class="language-html highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;video&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"myvideo.mp4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"myvideo.mp4"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"video/mp4"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;track&lt;/span&gt;
    &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"English"&lt;/span&gt;
    &lt;span class="na"&gt;kind=&lt;/span&gt;&lt;span class="s"&gt;"captions"&lt;/span&gt;
    &lt;span class="na"&gt;srclang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;
    &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/path/to/captions.vtt"&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;
  &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/video&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</description><author>William Huster</author><pubDate>Sat, 12 Aug 2023 17:00:00 GMT</pubDate><guid isPermaLink="true">https://williamhuster.com/automatically-subtitle-videos/</guid></item><item><title>An Idea for a Smart Contract Marketplace</title><link>https://mteam.space/posts/an-idea-for-a-smart-contract-marketplace/</link><description>In this post, I go over industry problems for token launchers and try to solve them with a smart contract marketplace design.</description><author>mteam's Writings</author><pubDate>Sat, 12 Aug 2023 16:41:06 GMT</pubDate><guid isPermaLink="true">https://mteam.space/posts/an-idea-for-a-smart-contract-marketplace/</guid></item><item><title>Life in 1080p</title><link>https://www.marginalia.nu/log/84_life_in_1080p/</link><description>&lt;p&gt;I recently got a smaller computer screen. It&amp;rsquo;s actually not &lt;em&gt;that&lt;/em&gt; small, it&amp;rsquo;s 27&amp;quot;, but the resolution is modest compared to what is available. And in short, it&amp;rsquo;s fantastic. It&amp;rsquo;s not an expensive screen, it&amp;rsquo;s not a fancy screen; but it&amp;rsquo;s comparatively a small screen.&lt;/p&gt;
&lt;p&gt;For a few years I was using a 34&amp;quot; ultra-wide monitor, which has been causing me nothing but grief. It&amp;rsquo;s sort of crept up on me that so many small annoyances in my computer-use all originated from using this screen. I want to elaborate on what&amp;rsquo;s been chafing. I want to be explicit that these are my experiences as someone who primarily uses a computer screen for programming. If you&amp;rsquo;re using your computer for something else, your experiences may differ.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Sat, 12 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/84_life_in_1080p/</guid></item><item><title>Message Queues, State Machines, Actors, UI</title><link>https://www.marginalia.nu/log/85-mq_sm_actor_ui/</link><description>&lt;p&gt;This is a bit of an &lt;em&gt;what I&amp;rsquo;ve been working on&lt;/em&gt; style of post. It&amp;rsquo;s also a bit of a complement for the
release notes of the upcoming release which should be dropping in a week or so. There&amp;rsquo;s some spit and
polish still missing from these things, but if I don&amp;rsquo;t write about them now too much will have been
ejected from the cache to make a well written post about it.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Sat, 12 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/85-mq_sm_actor_ui/</guid></item><item><title>Mach v0.2 released - Zig game engine &amp;amp; graphics toolkit</title><link>https://devlog.hexops.org/2023/mach-v0.2-released/</link><description>&lt;p&gt;Mach is a Zig game engine &amp;amp; graphics toolkit for building high-performance, truly cross-platform, robust &amp;amp; modular games, visualizations, and desktop/mobile GUI apps. &lt;a href="https://machengine.org/"&gt;Learn more&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ve been developing Mach for ~2 years; this release includes over a year of work, thousands of commits, and fixes &lt;a href="https://github.com/hexops/mach/milestone/2?closed=1"&gt;300 issues&lt;/a&gt;.&lt;/p&gt;
&lt;video height="800px" loop="loop"&gt;
&lt;source src="https://media.machengine.org/core/example/deferred-rendering.mp4" type="video/mp4" /&gt;
&lt;/video&gt;
&lt;h2 id="on-your-machine-in-just-60-seconds"&gt;On your machine in just ~60 seconds&lt;/h2&gt;
&lt;p&gt;With &lt;a href="https://machengine.org/about/zig-version/"&gt;this Zig nightly&lt;/a&gt; version you can run the above demo on your machine in ~60 seconds:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git clone https://github.com/hexops/mach-core
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; mach-core/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;zig build run-deferred-rendering
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href="https://machengine.org/about/goals/#zero-fuss-installation"&gt;Zero system dependencies&lt;/a&gt; to slow you down; only &lt;a href="https://machengine.org/about/zig-version/"&gt;zig&lt;/a&gt; is needed, we build and package the few relevant dependencies on our own. &lt;small&gt;&lt;a href="https://machengine.org/about/known-issues/"&gt;known issues&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
&lt;h2 id="engine-and-core-split"&gt;Engine and Core split&lt;/h2&gt;
&lt;p&gt;We completely split &lt;em&gt;Mach engine&lt;/em&gt; and &lt;em&gt;Mach core&lt;/em&gt; apart, so that you get to choose your journey and decide if you just want low-level window+input+GPU and nothing else, or prefer to use our higher level engine (which although not ready for use yet, will be very modular itself):&lt;/p&gt;
&lt;p align="center"&gt;
&lt;img class="color-auto" src="https://devlog.hexops.org/img/2023/mach-v0.2-released/img1.png" /&gt;
&lt;/p&gt;
&lt;h2 id="mach-core"&gt;Mach core&lt;/h2&gt;

&lt;source media="(prefers-color-scheme: dark)" /&gt;
&lt;img alt="mach-core" src="https://machengine.org/assets/mach/core-full-light.svg" style="height: 7rem;" /&gt;

&lt;p&gt;Mach core aims to be a &lt;em&gt;truly cross-platform&lt;/em&gt; way to get &lt;em&gt;window+input+GPU, and nothing else.&lt;/em&gt; It supports Linux, Windows, and Mac today, with WebAssembly / browser support in active development, and mobile coming in the future.&lt;/p&gt;
&lt;p&gt;It gives you the power of Vulkan, DirectX, Metal, and modern OpenGL in a &lt;em&gt;single concise graphics API and shader language&lt;/em&gt; - by compiling Google Chrome&amp;rsquo;s WebGPU implementation natively using Zig&amp;rsquo;s build system.&lt;/p&gt;
&lt;p&gt;Seamless multi-threading capabilities are provided, which means that your rendering and input handling are trivially decoupled from one another, you get butter-smooth window resizing, and your render loop and input handling can run at different frequencies. For example, a 60FPS render loop while your application handles keyboard &amp;amp; mouse events at a much faster dynamic rate (as fast as the OS can deliver them.)&lt;/p&gt;
&lt;div&gt;
&lt;video height="190px" loop="loop"&gt;
&lt;source src="https://media.machengine.org/core/example/gen-texture-light.mp4" type="video/mp4" /&gt;
&lt;/video&gt;
&lt;video height="190px" loop="loop"&gt;
&lt;source src="https://media.machengine.org/core/example/boids.mp4" type="video/mp4" /&gt;
&lt;/video&gt;
&lt;video height="190px" loop="loop"&gt;
&lt;source src="https://media.machengine.org/core/example/textured-cube.mp4" type="video/mp4" /&gt;
&lt;/video&gt;
&lt;/div&gt;
&lt;p&gt;You can think of Mach core as an alternative to the classic options of SDL, GLFW+OpenGL, etc.&lt;/p&gt;
&lt;p&gt;There are &lt;a href="https://machengine.org/core/examples/"&gt;15+ examples in the showcase&lt;/a&gt;, and we&amp;rsquo;re &lt;a href="https://github.com/hexops/mach/issues/858"&gt;planning a C API&lt;/a&gt; so it can be used from other languages as well.&lt;/p&gt;
&lt;h2 id="engine-development-has-begun"&gt;Engine development has begun&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Mach engine is not ready for use yet, but we&amp;rsquo;ve started breaking ground on higher-level engine APIs.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The v0.2 release focuses on deep changes and improvements to our infrastructure, primarily building out the Zig gamedev ecosystem and building foundational packages that we needed for Mach core, the engine, and a game we&amp;rsquo;re starting to build.&lt;/p&gt;
&lt;p&gt;As a result, we&amp;rsquo;ve &lt;em&gt;finally&lt;/em&gt; just broken ground on the engine side of things.&lt;/p&gt;
&lt;img src="https://devlog.hexops.org/img/2023/mach-where-we-are.png" /&gt;
&lt;h2 id="breaking-up-our-monorepo"&gt;Breaking up our monorepo&lt;/h2&gt;
&lt;p&gt;Previously, all of Mach&amp;rsquo;s &lt;a href="https://machengine.org/pkg/"&gt;standalone packages&lt;/a&gt; were developed in a single giant monorepo. This was both intimidating for new contributors, and we wanted to better communicate how many standalone Zig gamedev packages we actually provide.&lt;/p&gt;
&lt;p&gt;Today, we&amp;rsquo;re happy to report all standalone packages are now developed in separate repositories and available via the package manager!&lt;/p&gt;
&lt;h2 id="zig-package-manager"&gt;Zig package manager&lt;/h2&gt;
&lt;p&gt;We migrated 100% to the self-hosted Zig compiler and the new experimental Zig package manager, every Git submodule has been banished!&lt;/p&gt;
&lt;p&gt;We created &lt;a href="https://pkg.machengine.org/"&gt;pkg.machengine.org&lt;/a&gt; - a mirror for downloading Mach packages and Zig downloads as well.&lt;/p&gt;
&lt;h2 id="introducing-wrench-the-machanist"&gt;Introducing Wrench the Machanist&lt;/h2&gt;
&lt;img src="https://devlog.hexops.org/img/media/mach/wrench_rocket.svg" style="width: 300px;" /&gt;
&lt;p&gt;Wrench is the Mach engine mascot (artwork contributed by &lt;a href="https://keylajones.me"&gt;Keyla Jones&lt;/a&gt;); and also &lt;a href="https://wrench.machengine.org/"&gt;our infrastructure automation&lt;/a&gt; tool, written in Go, to help us with various tasks:&lt;/p&gt;
&lt;p&gt;Giving us an overview of our many repositories &lt;a href="https://wrench.machengine.org/projects/"&gt;CI statuses&lt;/a&gt; and &lt;a href="https://wrench.machengine.org/pull-requests/"&gt;pull requests&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/hexops/mach/assets/3173176/62c3e118-faa0-40c7-a663-e9e68e000bbf"&gt;&lt;img src="https://github.com/hexops/mach/assets/3173176/62c3e118-faa0-40c7-a663-e9e68e000bbf" style="width: 300px;" /&gt;&lt;/a&gt;&lt;a href="https://devlog.hexops.org/img/2023/mach-v0.2-released/img2.png"&gt;&lt;img src="https://devlog.hexops.org/img/2023/mach-v0.2-released/img2.png" style="width: 300px;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Sending us &lt;a href="https://github.com/hexops/mach/pull/953"&gt;pull requests&lt;/a&gt; to automatically update our CI pipelines to the latest Zig version, and update our &lt;a href="https://github.com/hexops/mach/pull/926"&gt;&lt;code&gt;build.zig.zon&lt;/code&gt;&lt;/a&gt; dependencies - in a fully atomic way across all our repositories at once:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://devlog.hexops.org/img/2023/mach-v0.2-released/img3.png"&gt;&lt;img src="https://devlog.hexops.org/img/2023/mach-v0.2-released/img3.png" style="width: 300px;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;hellip;and much more:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Checking our website and docs for &lt;a href="https://github.com/hexops/mach/issues/931"&gt;broken links&lt;/a&gt; and sending us GitHub issues.&lt;/li&gt;
&lt;li&gt;Performing automatic updates of involved dependencies, such as updating our fork of Google Chrome&amp;rsquo;s WebGPU implementation, which involves &lt;a href="https://github.com/hexops/mach-gpu-dawn/pull/22"&gt;pull requests&lt;/a&gt; across a few different repositories, pushing branches, running out-of-band commands, and ultimately presenting us with helpful/pretty diffs so we can just do the human work.&lt;/li&gt;
&lt;li&gt;A custom CI job runner system, running on a custom mini server with actual GPUs - so we can do screenshot-based testing of graphical applications in the future.&lt;/li&gt;
&lt;/ul&gt;
&lt;video controls="controls" height="800px"&gt;
&lt;source src="https://devlog.hexops.org/img/2023/mach-v0.2-released/img4.mp4" type="video/mp4" /&gt;
&lt;/video&gt;
&lt;video controls="controls" height="800px"&gt;
&lt;source src="https://devlog.hexops.org/img/2023/mach-v0.2-released/img5.mp4" type="video/mp4" /&gt;
&lt;/video&