<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://bridgetownrb.com/" version="2.1.2">Bridgetown</generator><link href="https://noelrappin.com/rss.xml" rel="self" type="application/atom+xml" /><link href="https://noelrappin.com/" rel="alternate" type="text/html" /><updated>2026-04-13T22:49:13+00:00</updated><id>https://noelrappin.com/rss.xml</id><title type="html">Noel Rappin Writes Here | Blog</title><subtitle>Write an awesome description for your new site here. It will appear in your document head meta (for Google search results) and in your feed.xml site description.</subtitle><entry><title type="html">The 2025 Book Post</title><link href="https://noelrappin.com/2026/03/2025-book-post/" rel="alternate" type="text/html" title="The 2025 Book Post" /><published>2026-03-06T00:00:00+00:00</published><updated>2026-03-06T00:00:00+00:00</updated><id>repo://blog.collection/_blog/2026-03-06-2025-book-post.md</id><content type="html" xml:base="https://noelrappin.com/2026/03/2025-book-post/">&lt;p&gt;One exciting thing about this year’s book wrap up, at least to me, is that I wrote mini-reviews all year, so these reviews should have a stronger tendency to sound like I actually read the book. And also this will come out in March and not, like, May.&lt;/p&gt;

&lt;p&gt;Another exciting thing, again, for me, is that I think I finally cracked my personal dilemma about How To Rate Books.&lt;/p&gt;

&lt;p&gt;So, this is like the nerdiest thing ever, but basically I’ve never quite known what to do with 5 star or 1-10 ratings for books.&lt;/p&gt;

&lt;p&gt;I have a pretty good system for finding books I will like, and I do, in fact, like about 90% or so of the books I read at least a little. So on a 5 star scale, basically everything is a 4. Storysite gets around this by allowing you to have quarter-point gradients, but it’s not clear to me that the difference between giving a book 4.25 and 4.5 stars carries any meaning. And expanding the scale so that most of the books that I like are 1-2 on the scale breaks with the understanding of what a “one-star” book is.&lt;/p&gt;

&lt;p&gt;Now, here’s where some of you are going to roll your eyes. Because a baseball analogy dawned on me. The scale is:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Out: I regretted reading this&lt;/li&gt;
  &lt;li&gt;Walk: I was neutral about this book&lt;/li&gt;
  &lt;li&gt;Single: On the whole I was glad to have read this&lt;/li&gt;
  &lt;li&gt;Double: I liked it and would recommend it easily&lt;/li&gt;
  &lt;li&gt;Triple: I will enthusiastically tell you about this book whether you like it or not&lt;/li&gt;
  &lt;li&gt;Home Run: This book became my personality for a while&lt;/li&gt;
  &lt;li&gt;Grand Slam: This book became my personality for a long time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I like this. It successfully gives the message that most of the things I finish have at least some value to me, while also making clear the distinction between a home run and a double. (I do seem to read a lot of doubles). It also extends nicely on both ends (strikeout, double play, three-run homer, grand slam), and lends itself nicely to descriptions like “double stretched to a triple” or “could have been a double, but the batter held at first” which to seem, at least to my weird brain, to be metaphorically useful. And which, you’ll be glad to know, I have generally restrained from in this post.&lt;/p&gt;

&lt;p&gt;And, best of all, I pretty quickly got to a place where I’d finish a book and my gut would say “single”.&lt;/p&gt;

&lt;p&gt;And so, off we go, this is every 2025 book that was a “double” or higher, plus a small smattering of books I rated “single” that I thought were worth talking about.&lt;/p&gt;

&lt;p&gt;Within each group, books are sorted by when I read them, which is not helpful to anybody except that any little references that I built up all year while writing mini reviews should still work.&lt;/p&gt;

&lt;p&gt;In some cases, where I thought it was fun, I threw in the first line of the book. I also think I got every case where the marketing blurb for the book referenced it as a combination of two other books, and we’re going to mock those for a while.&lt;/p&gt;

&lt;p&gt;Onward:&lt;/p&gt;

&lt;h2 id=&quot;single-&quot;&gt;Single +&lt;/h2&gt;

&lt;h3 id=&quot;a-passion-for-passion-by-alice-fraser&quot;&gt;&lt;a href=&quot;https://amzn.to/4srmtaR&quot;&gt;A Passion for Passion&lt;/a&gt; by Alice Fraser&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; Do you like satiric fake romance novels?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Did I Read This:&lt;/strong&gt; Fraser is one of my favorite funny people going right now.&lt;/p&gt;

&lt;p&gt;Alice Fraser is an Australian comedian and writer, probably best known in the US for being one of the regulars on &lt;a href=&quot;https://www.thebuglepodcast.com&quot;&gt;The Bugle podcast&lt;/a&gt;, but she’s also hosted a series of her own podcasts. Her podcast “The Last Post”, was a daily dispatch from an alternate reality that got stranger and stranger over time, and one of the features of that reality was romance author D’Ancey LaGuarde, whose regular book releases would be reported on the podcast.&lt;/p&gt;

&lt;p&gt;Fraser has an amazing ability to toss off extremely plausible and somewhat satiric novel synopses (her current podcast &lt;a href=&quot;https://www.thebuglepodcast.com/realms-unknown&quot;&gt;&lt;strong&gt;Realms Unknown&lt;/strong&gt;&lt;/a&gt; starts each episode with a fake description of an SF/F story in the genre being highlighted in that episode and I swear that a good 75% of them sound like they’d be outstanding). This book is a collection of descriptions of LaGuarde’s novels, and a sort of retrospective of her faux career and a few other romance related satires.&lt;/p&gt;

&lt;p&gt;The jokes here are both broad and specific, the kind of satire that comes from really knowing and loving a thing. And if they get a little repetitive they are still pretty good jokes.&lt;/p&gt;

&lt;h3 id=&quot;back-after-this-by-linda-holmes&quot;&gt;&lt;a href=&quot;https://amzn.to/49b4FJl&quot;&gt;Back After This&lt;/a&gt; by Linda Holmes&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; A woman goes on a bunch of dates for a podcast. Solid pitch, could easily have sold in the room.&lt;/p&gt;

&lt;p&gt;Linda Holmes had the possible misfortune to release her “woman goes on a bunch of dates for a podcast” novel, at roughly the same time as a separate “woman goes on a bunch of dates for a call-in radio show” novel was released (&lt;a href=&quot;https://amzn.to/45FapZG&quot;&gt;&lt;strong&gt;First Time Caller&lt;/strong&gt;&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Anyway, as a very, very long time Linda Holmes fan, I liked this one better.&lt;/p&gt;

&lt;p&gt;Our main character is a podcast producer – Holmes’ background as a podcaster for more than a decade makes this feel lived in. She’s talked into going on a series of dates for a new show while getting advice from an influencer, but at the same time she has a series of meet-cutes with different people, possibly accidentally including her actual true love.&lt;/p&gt;

&lt;p&gt;It’s a solid book, I liked that Holmes made the influencer character complex, the job dynamics seem real, and the relationship is charming.&lt;/p&gt;

&lt;h3 id=&quot;picks-and-shovels-by-cory-doctorow&quot;&gt;&lt;a href=&quot;https://amzn.to/49hQJvK&quot;&gt;Picks and Shovels&lt;/a&gt; by Cory Doctorow&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; Financial shenanigans in the tech world of the early 80s.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Did I Read This:&lt;/strong&gt; I need to come up with a better joke here than “because it’s Cory Doctorow”.&lt;/p&gt;

&lt;p&gt;I admit I don’t quite get what Doctorow is trying to do here. Which is not unusual for me, with respect to his fiction career. (I should say, I have tremendous respect for his fiction career.)&lt;/p&gt;

&lt;p&gt;This is the third book about Marty Hench, forensic accountant to the tech world, each book taking place earlier in the timeline, so this one is basically Marty’s origin story against the backdrop of the computer industry of the late 70s and early 80s, which I’ll admit at least some nostalgia for.&lt;/p&gt;

&lt;p&gt;(There’s actually way less connection between the three books than you’d think.. I mean, I have a notoriously bad memory for characters, but I did search the other books, and I don’t think there’s much overlap of the supporting characters). I’m not completely sure why we’re telling the story backwards, but I am also not Cory Doctorow.&lt;/p&gt;

&lt;p&gt;Anyway, Hench gets hired by a company that markets really bad computers to religious organizations, taking advantage of the religious community. Turns out the bad guys are being undercut by ex-employees who are reverse-engineering their stuff to remove lock-in.&lt;/p&gt;

&lt;p&gt;Hench realizes almost immediately who the bad guys are and switches sides. Things escalate. There is accounting and violence.&lt;/p&gt;

&lt;h3 id=&quot;the-grimoire-grammar-school-parent-teacher-association-by-caitlin-rozakis&quot;&gt;&lt;a href=&quot;https://amzn.to/4blSgUe&quot;&gt;The Grimoire Grammar School Parent Teacher Association&lt;/a&gt; by Caitlin Rozakis&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; Magic School. But Magic Elementary School. And your POV is the parent. The official blurb is “Big Little Lies goes to magic school”. While I could imagine that describing a book, it decidedly doesn’t describe &lt;em&gt;this&lt;/em&gt; book…&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do The Math:&lt;/strong&gt; Scholomance, but adorable?&lt;/p&gt;

&lt;p&gt;Do we have a consensus definition of “cozy” in “cozy fantasy”?&lt;/p&gt;

&lt;p&gt;Of course we don’t. It’s hard to do without being circular on the definition of cozy, but here goes.&lt;/p&gt;

&lt;p&gt;If something is billed as “cozy”, I expect it to cover most of these points:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Low stakes&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Relative lack of on-screen violence or gore. (Some would add on-screen sex)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Leans into a “cute” or “adorable” aesthetic&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;A sense of a community or found family, or of a character or characters finding their place in the world&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;A lack of cynicism, earnest sincerity tends to carry the day&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ve seen a definition that also suggests that a cozy fantasy can’t have a romance, but I think that’s too restrictive, there’s all kinds of cozy romances.&lt;/p&gt;

&lt;p&gt;Does that track? Score each of those on a 0,1,2 scale, add them up, and you get a 10 point scale.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Legends and Lattes&lt;/em&gt; scores about a nine, I think. &lt;em&gt;Can’t Spell Treason without Tea&lt;/em&gt; scores about seven? &lt;em&gt;Goblin Emperor&lt;/em&gt; scores… I make it six (it has higher stakes than the others). &lt;em&gt;Galaxy and The Ground Within&lt;/em&gt; scores about an 8.&lt;/p&gt;

&lt;p&gt;Which brings us to &lt;em&gt;GGSPTA&lt;/em&gt;, which I score as about an 7-8. But it also has a character who is deeply anxious about her child and whose marriage is really not doing well. Large chunks of this book did not make me think “cozy fireside read”, even though parts of it did, and parts of it are genuinely funny.&lt;/p&gt;

&lt;p&gt;Our main character, Vivian, had her life turned upside down when her kindergarten-aged daughter was bitten by a werewolf. She is directed to the title academy as a place where her child can get the attention she needs, but that also makes her an outsider among the somewhat insular parent body and there are so many things about the magic world that Vivian doesn’t know. Including a prophesy that seems to be targeting her daughter. This is sort of satire and sort of not, Vivian gets to a very dark place mid-book before eventually recovering herself. I’m not normally a fan of “we weren’t really in danger” as a plot resolution, but on reflection I think that the choice of actual plot mechanic is funny enough to overcome my reluctance.&lt;/p&gt;

&lt;p&gt;I liked it, maybe not as much as I hoped to, but I still liked it.&lt;/p&gt;

&lt;h2 id=&quot;double--i-would-recommend-these-books-to-myself-if-i-had-half-as-much-reading-time-as-i-do&quot;&gt;Double — I would recommend these books to myself if I had half as much reading time as I do&lt;/h2&gt;

&lt;h3 id=&quot;dungeon-crawler-carl-by-matt-dinniman&quot;&gt;&lt;a href=&quot;https://amzn.to/3N8AfyN&quot;&gt;Dungeon Crawler Carl&lt;/a&gt; by Matt Dinniman&lt;/h3&gt;

&lt;h3 id=&quot;carls-doomsday-scenario-by-matt-dinniman&quot;&gt;&lt;a href=&quot;https://amzn.to/46c3MOG&quot;&gt;Carl’s Doomsday Scenario&lt;/a&gt; by Matt Dinniman&lt;/h3&gt;

&lt;h3 id=&quot;the-dungeon-anarchists-cookbook-by-matt-dinniman&quot;&gt;&lt;a href=&quot;https://amzn.to/4aoZCp7&quot;&gt;The Dungeon Anarchist’s Cookbook&lt;/a&gt; by Matt Dinniman&lt;/h3&gt;

&lt;h3 id=&quot;the-gate-of-the-feral-gods-by-matt-dinniman&quot;&gt;&lt;a href=&quot;https://amzn.to/46qOkhI&quot;&gt;The Gate of the Feral Gods&lt;/a&gt; by Matt Dinniman&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; As noted online multiple times, this series is deeply resistant to elevator pitches&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Did I Read This:&lt;/strong&gt; It sounded ridiculous to me but a lot of people I trust recommended it including Alice Fraser, so there you go&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommended If You Like:&lt;/strong&gt; cats; absurd satire; deeply weird politics&lt;/p&gt;

&lt;p&gt;So, I think the first thing to say about this is that I read five books in this series this year (well, the last one actually bled over New Year’s) and my overall reaction is to say in my best Benoit Blanc Voice, “It makes no damn sense. Compels me, though”.&lt;/p&gt;

&lt;p&gt;In the first few pages, Earth is destroyed by unimaginably powerful aliens, who turn the entire planet into a video game, which is also streamed across the entire galaxy. Carl, our hero, has survived through sheer luck, and with the help of his cat Donut, who quickly becomes sentient, (don’t complain, the cat’s the best part…) now has to survive the game.&lt;/p&gt;

&lt;p&gt;He’s got stats, there are weapons and power-ups, and healing potions and NPCs and the whole slightly bonkers aesthetic of a video game crossed with the even-more bonkers aesthetic of reality TV.&lt;/p&gt;

&lt;p&gt;This book, as far as I can tell, does for LitRPG what &lt;em&gt;Legends and Lattes&lt;/em&gt; did for cozy fantasy, becoming a point of entry for a sub-genre that people didn’t even know they wanted.&lt;/p&gt;

&lt;p&gt;Is it good? That’s maybe the wrong question. It’s a lot of fun. Carl’s fine – he’s your basic SF hero who is extremely good at avoiding functional fixedness, but Donut is the real star.&lt;/p&gt;

&lt;p&gt;One thing that helps is that over the course of the books, Dinneman widens the story. It’s probably a bit much to say he is querying the premise, but he does take seriously the idea that a galactic society that puts together this kind of entertainment regularly is deeply messed up.&lt;/p&gt;

&lt;p&gt;Oh – and once I thoght of this this, I couldn’t unsee it. This book totally is the modern &lt;strong&gt;Battlefield Earth&lt;/strong&gt; – a nobody from Earth takes on a galactic power that has conquered Earth using nothing but his guile and wits. Dinneman is more tongue in cheek than Hubbard, but there’s a lot in common.&lt;/p&gt;

&lt;h3 id=&quot;tea-you-at-the-altar-by-rebecca-thorne&quot;&gt;&lt;a href=&quot;https://amzn.to/4pyxAfn&quot;&gt;Tea You at the Altar&lt;/a&gt; by Rebecca Thorne&lt;/h3&gt;

&lt;h3 id=&quot;alchemy-and-a-cup-of-tea-by-rebecca-thorne&quot;&gt;&lt;a href=&quot;https://amzn.to/45XzizN&quot;&gt;Alchemy and a Cup of Tea&lt;/a&gt; by Rebecca Thorne&lt;/h3&gt;

&lt;p&gt;These are book three and four of a series, you probably want to start at the &lt;a href=&quot;https://amzn.to/4kmCTgT&quot;&gt;beginning&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;“Will they or won’t they” is not the only romantic trope – “they already do and they are great together” is also a lot of fun. &lt;em&gt;The Thin Man&lt;/em&gt; is a beloved classic for a reason.&lt;/p&gt;

&lt;p&gt;I’ll note that this series, which I would consider cozy or cozy-ish, does have pretty high stakes.&lt;/p&gt;

&lt;p&gt;It does have a tight community focus, but only because people come from around the world to the location. There’s a relative lot of violence and tension. Whether it’s cute or not is an open call.&lt;/p&gt;

&lt;p&gt;Anyway, I like this series a bunch, I think the characters are fun (there aren’t many books that have a character willing to go with puns the way Keyanthe does…), and I love banter from characters who are already together.&lt;/p&gt;

&lt;p&gt;I don’t know this really as cozy as, say &lt;em&gt;Spellshop&lt;/em&gt;, but I do think it’s a lot of fun.&lt;/p&gt;

&lt;h3 id=&quot;the-tomb-of-dragons-by-katherine-addison&quot;&gt;&lt;a href=&quot;https://amzn.to/49p4dpB&quot;&gt;The Tomb of Dragons&lt;/a&gt; by Katherine Addison&lt;/h3&gt;

&lt;p&gt;I mean, I guess you have to admire Addison’s ability to continue to write stories in the &lt;em&gt;Goblin Emperor&lt;/em&gt; world and only give us small glimpses of the actual Goblin Emperor (Maia does make a minor appearance here, which was welcome).&lt;/p&gt;

&lt;p&gt;Instead, we continue to follow Celehar, a perfectly good character who has perfectly good adventures – he bargains with a ghost dragon.&lt;/p&gt;

&lt;p&gt;(I’m mostly kidding here, I don’t like bugging authors for the books they don’t write, but Maia is one of my five favorite characters of the last decade or so, and I kind of would love to know what’s happening with him.)&lt;/p&gt;

&lt;p&gt;This is one of those books that doesn’t quite go where you think it’s going to go – the ghost dragon moves things along quite a bit. I liked it, and I curious what Addison will do now that this series also seems closed.&lt;/p&gt;

&lt;h3 id=&quot;when-the-moon-hits-your-eye-by-john-scalzi&quot;&gt;&lt;a href=&quot;https://amzn.to/45D1ebZ&quot;&gt;When the Moon Hits Your Eye&lt;/a&gt; by John Scalzi&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; The moon turns to cheese. It’s funny til it’s not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommended If You Like:&lt;/strong&gt; cheese-based humor; weird apocalypses&lt;/p&gt;

&lt;p&gt;This one shouldn’t work at all. But actually it’s pretty gouda. That’ll brie the last cheese pun.&lt;/p&gt;

&lt;p&gt;The premise is that suddenly the moon turns to cheese. Not a metaphor. Actual cheese. Not just the actual moon, but all moon rocks on earth also change.&lt;/p&gt;

&lt;p&gt;You would think this is the setup for a ridiculous comedy, and it starts that way. Each chapter of the book is a different vignette, more or less one a day. They do intertwine, but basically they are all kind of standalone.&lt;/p&gt;

&lt;p&gt;Then it gets a little more bigger in scope as people grapple with what it means to have moon suddenly change into cheese. The book is careful to point out that just because people don’t know how the change happened yet, doesn’t mean that there isn’t a rational explanation for it somewhere.&lt;/p&gt;

&lt;p&gt;Eventually the smart people realize that the cheese moon is unstable, suddenly getting a lot less ridiculous and turning the book into the most delicious disaster novel ever.&lt;/p&gt;

&lt;p&gt;This book succeeds at what it’s trying to do to kind of an astonishing degree – make you take seriously a genuinely goofy premise. I liked this more than Scalz’s more serious novel this year.&lt;/p&gt;

&lt;h3 id=&quot;the-spellshop-by-sarah-beth-durst&quot;&gt;&lt;a href=&quot;https://amzn.to/4qExOm6&quot;&gt;The Spellshop&lt;/a&gt; by Sarah Beth Durst&lt;/h3&gt;

&lt;h3 id=&quot;the-enchanted-greenhouse-by-sarah-beth-durst&quot;&gt;&lt;a href=&quot;https://amzn.to/4syE8fZ&quot;&gt;The Enchanted Greenhouse&lt;/a&gt; by Sarah Beth Durst&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; The coziest fantasy that has ever been cozied. The blurb says “Like a Hallmark rom-com full of mythical creatures and fueled by cinnamon rolls and magic” – seems about right.&lt;/p&gt;

&lt;p&gt;“Cozy” is an inherently vague word, but if it doesn’t apply to this book, I’m not sure what it would possibly apply to. On the cozy scale I listed above, this scores like an 11 out of 10. It’s so cozy, that for the rest of the year, I assumed that I had written the cozy definition seen above for this book and was surprised when I went back and saw that I had written it for a different book.&lt;/p&gt;

&lt;p&gt;Our heroine is a librarian at one of those great fantasy libraries of magic but we don’t learn much about it because it’s sacked by rebels on page 2, and she escapes with her life, a boatful of books and her friend, who is a sentient spider plant. I’m going to go out on a limb and suggest that a sarcastic sentient spider plant is very much “cute aesthetic”&lt;/p&gt;

&lt;p&gt;With no other options, she returns to the outlying isle of her birth, which has mer-horses, adorable bakeries, and a deep problem with the lack of magic help coming from the main empire.&lt;/p&gt;

&lt;p&gt;Can she find allies, friends, and more than friends, while solving the problem and thwarting those who would do harm? This is the kind of book where the nominally dark and forbidding forest actually turns out to be populated by adorable animal-shaped sprites. Where the various characters have skin in all shades (our main character is blue – to be clear, I thought this part was awesome).&lt;/p&gt;

&lt;p&gt;I’m not immune to the charms of a sentient spider plant, and I had a good time with this one, but it’s kind of thin. But adorable. Probably this year’s winner of the “light but cute but light but cute” award.&lt;/p&gt;

&lt;p&gt;As for the sequel, if you read &lt;em&gt;The Spellshop&lt;/em&gt; and think “good book, but not enough talking plants”, then boy howdy is this sequel for you.&lt;/p&gt;

&lt;p&gt;Our sequel concerns the woman who created the sentient spider plant. In &lt;em&gt;The Spellshop&lt;/em&gt; we learn her punishment was being turned into a statue. In this book, she gets better and finds herself on an island with a bunch of magical greenhouses stocked with sentient plants and one grumpy gardener.&lt;/p&gt;

&lt;p&gt;If you look at my Cozy criteria, the sequel scores about a 14 out of 10:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Low stakes – for most of the book there are only two human characters, the stakes couldn’t possibly be lower. Huge, epic revolution is far, far in the background.&lt;/li&gt;
  &lt;li&gt;Low violence – yeah, no violence&lt;/li&gt;
  &lt;li&gt;Cute aesthetic – the book has a sassy talking rose plant, so I’m going to go with “check”&lt;/li&gt;
  &lt;li&gt;Found family / Community – check and check, literally in this case in that the male lead basically finds his family&lt;/li&gt;
  &lt;li&gt;Lack of cynicism — yep, that too.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s also a prototypical grumpy/sunshine romance.&lt;/p&gt;

&lt;p&gt;And for all that, the book basically works. (There’s a little too much fretting about whether she’s going to get in trouble for breaking the laws about magic that we, the reader, know are already dead letters). But I actually like the magic system in these books, there are a lot of descriptions of plants, and it’s got small dragons. It’s fine, it’s all fine.&lt;/p&gt;

&lt;h3 id=&quot;one-death-at-a-time-by-abbi-waxman&quot;&gt;&lt;a href=&quot;https://amzn.to/49bxR2W&quot;&gt;One Death at a Time&lt;/a&gt; by Abbi Waxman&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Do The Math:&lt;/strong&gt; Hacks + Erin Brockovich + Knives Out&lt;/p&gt;

&lt;p&gt;I think so much about books that are surprises or disappointments, that it’s nice to sometimes find a book that is exactly what I think it will be.&lt;/p&gt;

&lt;p&gt;Waxman, who has written a lot of romance or romance-adjacent books, takes a swing at a murder mystery. Our amateur detectives are… well, quite a pair. The famous one is an actress in her sixties, once a huge star, then wrongfully imprisoned for killing her husband.&lt;/p&gt;

&lt;p&gt;On her release, she’s become a lawyer, specializing in advocating for the wrongly accused. The other one is her AA sponsor, who becomes her personal assistant.&lt;/p&gt;

&lt;p&gt;The characters are great and feel unique, Waxman has always been good at narration. I’m not 100% sure I completely understood the through line of the plot, but this is a setup for a series if I’ve ever seen one, and I hope it gets there.&lt;/p&gt;

&lt;h3 id=&quot;great-big-beautiful-life-by-emily-henry&quot;&gt;&lt;a href=&quot;https://amzn.to/3MPXHRo&quot;&gt;Great Big Beautiful Life&lt;/a&gt; by Emily Henry&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Recommended If You Like:&lt;/strong&gt; Taylor Jenkins Reid fans should feel at home, a lot of people noted the similarity to &lt;em&gt;The Seven Husbands of Evelyn Hugo&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Award For:&lt;/strong&gt; Goodreads Reader Favorite for Romance&lt;/p&gt;

&lt;p&gt;This is Emily Henry’s sixth adult romance novel. Five have won Goodreads awards for best romance (all but the first, so five in a row). I’d say she’s established herself as the most popular romance author in the country right now. And there’s a reason for it – she’s really good.&lt;/p&gt;

&lt;p&gt;One thing that happened to me this year is that I got just a bit burned out on romance books, but I stuck with a few authors that I really like, and Henry is one.&lt;/p&gt;

&lt;p&gt;Our main character here, Alice, is a journalist. She’s obsessed with Margaret Ives, a midcentury celebrity, a notorious heiress who burned bright, burned bridges, and has been out of the public eye for decades. Alice has tracked her down and is trying to secure the rights to write a biography. Hayden, an award winning biographer, has also been invited, and our heiress declares that she will spend one month working with both of them before announcing which one she trusts with her story.&lt;/p&gt;

&lt;p&gt;As somebody online pointed out, a strict NDA is one way to keep your romance characters from fully communicating…&lt;/p&gt;

&lt;p&gt;The book bounces back and forth from Alice’s perspective to Margaret’s story, ostensibly from the eventual biography. Alice and Hayden fall for each other because of course they do. Henry does a very good job of creating a plausible and interesting backstory for Margaret (also, the description of Hayden’s previous award winning book seems quite plausible in a way that is hard to do).&lt;/p&gt;

&lt;p&gt;Your enjoyment here is going to depend somewhat on your patience for sorting out the details of Margaret’s story, there’s a lot of backstory and a complex family tree. Alice and Hayden have a good story but Margaret is a good third of the book and if you aren’t into that part, it’s going to feel slow. I liked it, so I liked it.&lt;/p&gt;

&lt;h3 id=&quot;problematic-summer-romance-by-ali-hazelwood&quot;&gt;&lt;a href=&quot;https://amzn.to/49d9rpK&quot;&gt;Problematic Summer Romance&lt;/a&gt; by Ali Hazelwood&lt;/h3&gt;

&lt;p&gt;As I said, I did kind of burn out on romance novels this year, no particular reason, it happens sometimes. Ali Hazelwood was another one I kept up with and she had &lt;em&gt;four&lt;/em&gt; books come out this year, of which this was probably my favorite. (&lt;strong&gt;Deep End&lt;/strong&gt; was also good).&lt;/p&gt;

&lt;p&gt;One of the reasons I started reading romance novels was to try to understand how they work – what does an author do to make a story compelling against a backdrop of a lot of expected plot beats. I’ve never felt that it’s a problem for a story to have expected plot beats, but it does place the burden on the author to make me care what happens anyway.&lt;/p&gt;

&lt;p&gt;This book is a follow-up to &lt;em&gt;Not In Love&lt;/em&gt;, our viewpoint character is Maya, she’s the 24 year old much younger sister of the previous book’s main character. She’s had a probably-requited crush on Conor, her brother’s 38 year old best friend, for about four years. The age gap is the “problematic” in the title. We have a lot of Hazelwood tropes here – Maya is in STEM, though she’s bolder and less quirky than some other Hazelwood characters. The MMC is emotionally unavailable in part because he’s covering for how much he’s crushing on the FMC.&lt;/p&gt;

&lt;p&gt;In most of the other Hazelwood books, though, the characters start as rivals, and in this case they are already close friends. Maya’s viewpoint voice is strong and funny and determined to break down Conor’s resistance. Hazelwood sets this in Sicily, a part of the world she clearly loves, there’s enough background wedding drama to keep things moving. Overall, I very much enjoyed this. This book has a fantastic cover.&lt;/p&gt;

&lt;h3 id=&quot;atmosphere-by-taylor-jenkins-reid&quot;&gt;&lt;a href=&quot;https://amzn.to/4sAbfkr&quot;&gt;Atmosphere&lt;/a&gt; by Taylor Jenkins Reid&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Why Did I Read This:&lt;/strong&gt; I’ve liked Reid in the past, and the subject matter is definitely up my alley&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do The Math:&lt;/strong&gt; Apollo 13 + 12 years + romantic subplot&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Award For:&lt;/strong&gt; Goodreads Reader Favorite for Historical Fiction&lt;/p&gt;

&lt;p&gt;I have two quibbles right off the bat:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;The book centers around a fictional space shuttle accident in December 1984. While of course you can write about any historical fiction thing you want, as somebody who pretty clearly remembers Challenger, which happened in January 1985, this felt a little weird.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I can’t remember the other one. It’ll come to me. Wait, I’ve got it. We get the viewpoint of two astronauts who actually go into space and the both separately come to the conclusion that, all things considered, they’d rather be on the ground. That’s certainly a choice.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I can’t speak to the technical accuracy. Reid says flat-out at the end that she took significant artistic license. My guess is the facts of the accident are broadly plausible for “melodrama” levels of plausible and that she played pretty fast and loose with the NASA staffing rules to have an astronaut in space for one mission and then as the main Mission Control contact on the next mission six weeks later. (They did used to run missions that close together, though). That’s just a guess, though.&lt;/p&gt;

&lt;p&gt;Anyway… Our main character is Joan, and we meet her in 1979 as an astronaut trainee and in 1984 at Mission Control during the aforementioned accident.&lt;/p&gt;

&lt;p&gt;On board the shuttle is another astronaut named Vanessa, and as we move in the older timeline, we see Joan and Vanessa fall in love, which at that time meant absolute secrecy. So we go back and forth between the accident in 1984, and the 1979-1984 timeline gradually revealing the relationship between the two.&lt;/p&gt;

&lt;p&gt;There’s nothing exactly surprising that happens, though I have to admit that it somewhat harrowingly depicts what a same-sex romance against the background of the early 80s needed to look like. It wasn’t &lt;em&gt;that&lt;/em&gt; long ago, I was alive, if not adult.&lt;/p&gt;

&lt;p&gt;Nothing surprising, but it’s all quite well done, it’s easy to root for the characters (though there are a couple of bits with side characters that don’t really go anywhere) and when it comes time for the big speech, the big speech delivers. Honestly, what more can you ask for?&lt;/p&gt;

&lt;h3 id=&quot;the-potency-of-ungovernable-impulses-by-malka-older&quot;&gt;&lt;a href=&quot;https://amzn.to/4sEle8l&quot;&gt;The Potency of Ungovernable Impulses&lt;/a&gt; by Malka Older&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Why Did I Read This:&lt;/strong&gt; See previous raves in previous years…&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opening Lines:&lt;/strong&gt; “A storm was writhing over Valdegeld, its tendrils churning Giant’s ever-present fog and pressing sleet and freezing rain through the atmoshield and onto the august buildings of Valdegeld University and auxiliaries, including my—less august, but evocative and comfortable—lodgings.”&lt;/p&gt;

&lt;p&gt;Last year for book two of this series, I talked about how beautiful the writing in this series is.&lt;/p&gt;

&lt;p&gt;This year, I want to talk about how beautiful the world building is.&lt;/p&gt;

&lt;p&gt;The mystery is great, I was originally somewhat skeptical of Older spending so much time with Mossa and Pleity separated, but in the end I think it was mostly worth it to get to see Pleity investigate on her own.&lt;/p&gt;

&lt;p&gt;So, separate from that I want to talk about the world building here, because I think Older does something subtle and heartbreaking here. We have this Jovian community that is in many ways lovely – not tremendously prosperous, perhaps, but growing and stable. Pleity is a scholar. She’s a Classical scholar, which means she studies old Earth. We are given to believe that Classical scholars are much more respected than Modern scholars, who study Jupiter and also do stuff like, you know, science.&lt;/p&gt;

&lt;p&gt;Pleity’s research is part of the ongoing Classical project to re-introduce life back to earth. Specifically, Pleity is attempting to research things like the normal proportions of different animals in a forest from textual analysis of novels. I think that we, the readers, are supposed to believe this is a doomed pursuit, but in-universe, Pleity is a highly respected scholar. When she meets a Modern anthropologist of Jupiter, she’s confused – why would anybody want to study the people of Jupiter?&lt;/p&gt;

&lt;p&gt;Older does a thing, where a character says that something was a “deeply human” thing to do, but they don’t mean it the way you or I might say it, they mean it to mean “carelessly destructive”. And eventually I realize it – this culture is deeply traumatized and suffering from collective survivors’ guilt. And it’s a background note, but it’s such a thoughtfully played background note. This series is beautiful.&lt;/p&gt;

&lt;h3 id=&quot;the-last-hour-between-worlds-by-melissa-caruso&quot;&gt;&lt;a href=&quot;https://amzn.to/45cTBJi&quot;&gt;The Last Hour Between Worlds&lt;/a&gt; by Melissa Caruso&lt;/h3&gt;

&lt;h3 id=&quot;the-last-soul-among-wolves-by-melissa-caruso&quot;&gt;&lt;a href=&quot;https://amzn.to/4pNppfh&quot;&gt;The Last Soul Among Wolves&lt;/a&gt; by Melissa Caruso&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Why Did I Read This:&lt;/strong&gt; Another recommendation from Alice Fraser&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opening Lines:&lt;/strong&gt; “It’s easy to fall into the wrong world”&lt;/p&gt;

&lt;p&gt;The “If I had a nickel meme” but for books where Raven is a symbol for learning. To quote the dragon in &lt;em&gt;Raven Scholar&lt;/em&gt;, “there are other birds”.&lt;/p&gt;

&lt;p&gt;This book features a thing you don’t see all that often in fantasy worlds – the main character has a two-month-old, and she’s on her first night away, so when the book starts, she’s already tired, happy to be out, feeling guilty, and also has no idea how to talk to people anymore. (It’s some slightly modern diction for a more Victorian-era setting, but I mean, that’s, like, every fantasy book.)&lt;/p&gt;

&lt;p&gt;She’s at what you can think of as a New Year’s Eve party when things get weird. And by weird, nearly the entire party dies of poisoning, then the clock strikes the hour and they are get better, but the entire party has shifted to another level of reality. Which is, apparently a thing that can happen in this world, which has multiple layers of reality stacked on top of each other.&lt;/p&gt;

&lt;p&gt;We then get a kind of a mash up of a time-loop, a game of Traitor (since somebody in the party is triggering the changes), and some weird magic things. Also there’s a romance, which is not the main point, but which worked. (I’m usually all in on “they met as kids but as adults they don’t recognize each other” as a trope.)&lt;/p&gt;

&lt;p&gt;Caruso does a good job of making each loop distinct, which is good, because there are like 11 of them, and the magic system is unique and mostly interesting (there are times where it feels contrived, and times where it seems less weird than maybe it presents itself to be).&lt;/p&gt;

&lt;p&gt;Still, the mystery was good, the ending was satisfying.&lt;/p&gt;

&lt;p&gt;As for book two, this is going to sound like a dig, but I mean it as a compliment – Caruso does a fantastic job here coming up with a premise that is both obviously related to the first book in the series, but which is its own thing with it’s own plot arcs and story beats.&lt;/p&gt;

&lt;p&gt;This time round, Kem is called upon by an old friend to help with what turns out to by your run-of-the-mill “all your friends got inscribed in a cursed magic book, and now all the people in the book are going to die except one, who gets to own the magic book”. That old thing.&lt;/p&gt;

&lt;p&gt;This builds on the events of the first book, it feels more like it’s setting up a long ongoing series than a trilogy, but I don’t really know what Caruso is planning. I’m in, though, this series is good.&lt;/p&gt;

&lt;p&gt;Wait, sometimes this information is, you know, available. From the author’s website: “The current plan is for three books! It’s the sort of series I could expand indefinitely if the opportunity arose, though.”&lt;/p&gt;

&lt;h3 id=&quot;automatic-noodle-by-annalee-newitz&quot;&gt;&lt;a href=&quot;https://amzn.to/3LjO6lg&quot;&gt;Automatic Noodle&lt;/a&gt; by Annalee Newitz&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; Robots open a noodle restaurant in future San Francisco&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommended If You Like:&lt;/strong&gt; Noodles, of course, but also robots&lt;/p&gt;

&lt;p&gt;So, given the cozy scale referenced above, this scores an 8 or a 9. You don’t see cozy SF as often as you see cozy fantasy, that’s for sure.&lt;/p&gt;

&lt;p&gt;It’s about a group of robots that decide to start a noodle restaurant in a future San Francisco still recovering from California’s war of independence.&lt;/p&gt;

&lt;p&gt;The robots are “human-equivalent” intelligence, and are thus nominally free under California law, but can’t own property, and are subject to discrimination, like, if somebody thinks your noodle restaurant is run by robots, you might get review-bombed. This is a novella, and there’s not a lot of plot, it’s mostly vibes, it’s a found family story where the family is robots. Good vibes, it’s a very sweet story.&lt;/p&gt;

&lt;p&gt;One of my new fascinations is the difference between fictional AI and real world AI. Newitz is obviously sympathetic to the robots here and their desire to just run a restaurant and feed people and eventually create a community space. Newitz is also very much against the use of LLMs in creative fields at the moment. I’m not saying the positions are opposed, you can articulate why the two things are different and should be treated differently. But I still think it’s interesting that the idea of an artificial sentient is still so compelling, even with our artificial non-sentient LLMs. I also think it’s interesting how little the science fiction conception of AI has in common with AI as we are all currently living it.&lt;/p&gt;

&lt;h3 id=&quot;little-bosses-everywhere-by-bridget-read&quot;&gt;&lt;a href=&quot;https://amzn.to/4jLu8g4&quot;&gt;Little Bosses Everywhere&lt;/a&gt; by Bridget Read&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; Hate Muti-Level Marketing? You don’t know the half of it.&lt;/p&gt;

&lt;p&gt;Well executed history of Multi-Level Marketing in the USA from its emergence out of the world of direct door-to-door sales to its current status as evil behemoth with too much political clout. The author is pretty clear that there is no actual distance between legal MLMs and illegal pyramid schemes and the only reason the MLM’s have managed to stay legal is through a combination of obfuscation and buying influence. Kind of a grim story, actually.&lt;/p&gt;

&lt;h3 id=&quot;royal-gambit-by-daniel-omalley&quot;&gt;&lt;a href=&quot;https://amzn.to/3Njf9hn&quot;&gt;Royal Gambit&lt;/a&gt; by Daniel O’Malley&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; The Rook is back!&lt;/p&gt;

&lt;p&gt;For the second time in a row, I only found out about a new Daniel O’Malley book after the fact, which was weird.&lt;/p&gt;

&lt;p&gt;Anyway, this is book four and I guess it’s a universe now, with four stories that largely don’t share protagonists. For the record, this book is more of a sequel to book 2 with its Grafters and its royal family ending, so book 3 is more of a side quest.&lt;/p&gt;

&lt;p&gt;Our viewpoint character is Alix. Like most children in this world who exhibit supernatural abilities in the UK, she’s taken by the Checquy as a small child. Unlike most, because she is of a noble family, the Checquy finds it useful to sort of embed her as a playmate of the princess, which has basically earned Alix the enmity of all sides – her Checquy peers distrust her because she had extra privileges as a child, and her parents are intent on mining her for all the power they can extract from the Checquy, and the princess is a little skeptical of her because she is obviously keeping a secret.&lt;/p&gt;

&lt;p&gt;As we start, the actual Prince of Wales is supernaturally murdered, and Alix is both investigating the crime and protecting her friend the princess. This is more of a thriller/police novel than the other Rook books, and it’s a pretty good one. A nice side effect of O’Malley’s somewhat, um, exuberant descriptions of the supernatural in these books, is that the range of possibilities for what this killer is capable of is nigh-infinite and the investigators seem truly terrified. I also think the killer’s motivations were both surprising and interesting. It did have a little less of the joyful weird descriptions of past Rook books, and I did miss that, but overall, fun.&lt;/p&gt;

&lt;h3 id=&quot;hemlock--silver-by-t-kingfisher&quot;&gt;&lt;a href=&quot;https://amzn.to/3LMqvdf&quot;&gt;Hemlock &amp;amp; Silver&lt;/a&gt; by T. Kingfisher&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; Kingfisher takes on Snow White. Kinda.&lt;/p&gt;

&lt;p&gt;This based on Snow White in that it has a character named Snow, a magic system involving mirrors, and something like a poisoned apple.&lt;/p&gt;

&lt;p&gt;Everything else is different, which is something of a Kingfisher hallmark these days.&lt;/p&gt;

&lt;p&gt;Look, I enjoyed this book, the characters are charming, the magic is creepy and unique, there’s a couple of real nightmare images. Does it have a lot of micro tropes that are, like, common to Kingfisher’s books? Yes. Did I mind? No.&lt;/p&gt;

&lt;h3 id=&quot;the-killer-question-by-janice-hallett&quot;&gt;&lt;a href=&quot;https://amzn.to/45gdD5N&quot;&gt;The Killer Question&lt;/a&gt; by Janice Hallett&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; Hallett takes on the high-stakes, hard-driving world of English pub quizzes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommended If You Like:&lt;/strong&gt; I feel these books are choose your own adventure adjacent in I way I can’t quite articulate&lt;/p&gt;

&lt;p&gt;This is another of Hallett’s murder mysteries through found texts, in this case a wannabe documentarian is sending information to a Netflix producer to convince Netflix to fund his documentary about a mysterious death at a pub in rural England.&lt;/p&gt;

&lt;p&gt;As is the case with other of Hallett’s novels, we have a combination of text threads, emails, recorded text and other records, and as with her other books, information is very much withheld from you to make the plot points pop. This book is really upfront about that – we get an email from the documentarian from time to time telling about the amazing upcoming twist.&lt;/p&gt;

&lt;p&gt;As a result, this one is less of a fair play mystery than other Hallett books.&lt;/p&gt;

&lt;p&gt;Anyway, I basically liked it – your milage may vary on the big twist and the general structure, but it’s fun and twisty, and as a little bonus it ends up being surprisingly gentle to a character that you think is being made fun of most of the book.&lt;/p&gt;

&lt;h3 id=&quot;the-impossible-fortune-by-richard-osman&quot;&gt;&lt;a href=&quot;https://amzn.to/4t2aS2e&quot;&gt;The Impossible Fortune&lt;/a&gt; by Richard Osman&lt;/h3&gt;

&lt;p&gt;The first thing I want to say about this book is that what Osman is doing here is really hard.&lt;/p&gt;

&lt;p&gt;It’s a mystery novel where at least a dozen characters are, at least briefly, viewpoint characters. The main characters are incredibly charming and the characterization is very precise. You know which characters are good at things that others are bad at, and it never seems to be for plot contrivance.&lt;/p&gt;

&lt;p&gt;He makes it look easy, but if it were that easy, I’d read 50 books a year that do it instead of, like three.&lt;/p&gt;

&lt;p&gt;Anyway, the character stuff here continues to be good and genuinely touching – especially in this case the focus on one of the mother-daughter relationship. My main quibble here is the plot itself, it’s got a dash of “the stakes were never really as high as everybody thought”.&lt;/p&gt;

&lt;p&gt;There’s two stories that you think are going to intertwine, but they don’t really, and Osman at the end gives a little “you thought the flashy story was the real story, but actually it was this other thing”. He almost pulls it off, but not quite.&lt;/p&gt;

&lt;p&gt;Don’t get me started about the Netflix movie…&lt;/p&gt;

&lt;h3 id=&quot;what-stalks-the-deep-by-t-kingfisher&quot;&gt;&lt;a href=&quot;https://amzn.to/49HHNAd&quot;&gt;What Stalks the Deep&lt;/a&gt; by T. Kingfisher&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; What if Miles Vorkosigan, but gothic horror&lt;/p&gt;

&lt;p&gt;Book three in the Sworn Soldier series. This time, our hero Alex is in West Virginia investigating a very creepy mine that turns out to be somewhat Lovecraftian.&lt;/p&gt;

&lt;p&gt;Not a lot really to say about this, except to marvel that the same person can put out “Wizard’s Guide to Defensive Baking”, “Swordheart”, and this.&lt;/p&gt;

&lt;h3 id=&quot;we-had-a-hunch-by-tom-ryan&quot;&gt;&lt;a href=&quot;https://amzn.to/3YLjkoI&quot;&gt;We Had A Hunch&lt;/a&gt; by Tom Ryan&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Why Did I Read This:&lt;/strong&gt; There are a few sentences that pretty much guarantee I’ll consider your book and one of them is “Former teen detective grows up”.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do The Math:&lt;/strong&gt; The official marketing says “Nancy Drew meets Yellowjackets”, and I guess Yellowjackets is a stand in here for “adult characters dealing with weird teenage trauma”?&lt;/p&gt;

&lt;p&gt;In this case, we’ve got three teen detectives, the Van Dyne twins, your classic “children of the police chief” teen detectives and teen hacker and reluctant detective Joey O’Day.&lt;/p&gt;

&lt;p&gt;25 years ago they got in way, way over their head and as a result, the twins’ father and one of their boyfriends were killed.&lt;/p&gt;

&lt;p&gt;Now, 25 years later, they’ve gone their mostly separate ways. But a copycat is back in their small town….&lt;/p&gt;

&lt;p&gt;Strong premise. If you think it would be a great setup for, say, an eight episode Netflix series, good news, it’s already been optioned. I tore through this one pretty quickly, and I think it’s triple-level for about the first 80%. Eventually we reveal both the original killer and the new killer, and I think one of them was well-planted, and one of them is maybe a little more dubious. (Both of them depend on the characters being sociopaths and able to lie convincingly for a long time, which, fair enough I guess).&lt;/p&gt;

&lt;p&gt;Still the end didn’t quite hold together for me, I think it drops to a double. It caused me to go back and read Tom Ryan’s previous adult mystery.&lt;/p&gt;

&lt;h3 id=&quot;brigands--breadknives-by-travis-baldree&quot;&gt;&lt;a href=&quot;https://amzn.to/4spTIeD&quot;&gt;Brigands &amp;amp; Breadknives&lt;/a&gt; by Travis Baldree&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Opening Lines:&lt;/strong&gt; “Fuck,” cried Fern, ducking back inside the carriage a whisker before a clawed and scaled hand sailed past.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommended If You Like:&lt;/strong&gt; Talking cutlery, emotionally unavailable heroes, basically if you like the T. Kingfisher White Rat books&lt;/p&gt;

&lt;p&gt;Just so we’ve all got this straight, Baldree released &lt;em&gt;Legends &amp;amp; Lattes&lt;/em&gt; in 2022, and basically kick-started a new subgenere of low-stakes fantasy stories.&lt;/p&gt;

&lt;p&gt;By his own admission, he tore up his first try at a sequel and eventually wrote &lt;em&gt;Bookshops &amp;amp; Bonedust&lt;/em&gt;, which is a prequel. (Not that you asked, but all three books style themselves with the ampersand in the title).&lt;/p&gt;

&lt;p&gt;This book takes place after &lt;em&gt;Legends &amp;amp; Lattes&lt;/em&gt; and features Fern, introduced as a bookseller that Viv befriends in &lt;em&gt;Boookshps &amp;amp; Bonedust&lt;/em&gt;. Fern is moving to open a shop next to Viv, which starts to look like &lt;em&gt;Legends &amp;amp; Lattes&lt;/em&gt;, but when that all goes very smoothly you start to expect that something else is going on.&lt;/p&gt;

&lt;p&gt;Fern is dissatisfied as a bookseller, and literally stumbles into an adventure through the expedient of drunkenly falling asleep in a wagon belonging to Astryx, a famous hero and bounty hunter.&lt;/p&gt;

&lt;p&gt;Let’s go to the scoreboard:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Disaffected middle-age heroine&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Emotionally unavailable hero&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Forced on a common quest&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Cutlery with a human soul that talks&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Cute-coded non-human chaotic creature&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Oh – I thought, Baldree has written a T. Kingfisher novel. It’s not quite as good as, say, the Paladin books, but it’s in the ballpark. It’s more violent than the previous two in this series, I should warn.&lt;/p&gt;

&lt;p&gt;I was in on the emotional conclusion, so I guess that’s good.&lt;/p&gt;

&lt;h3 id=&quot;higher-magic-by-courtney-floyd&quot;&gt;&lt;a href=&quot;https://amzn.to/4sNZNS3&quot;&gt;Higher Magic&lt;/a&gt; by Courtney Floyd&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; Can you have cozy dark academia? The official blurb says “cozy dark academia novel for fans of Heather Fawcett’s Emily Wilde series and R.F. Kuang’s Babel”… which, thank you, you have suggested a cozy novel and a dark novel, both of which take place in the past… which this book does not. So, shrug?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Did I Read This:&lt;/strong&gt; Author Max Gladstone recommended it highly&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opening Lines:&lt;/strong&gt; “The classroom door shimmered, and I scowled at it”&lt;/p&gt;

&lt;p&gt;This book, and I say this with love, is subtle as a brick.&lt;/p&gt;

&lt;p&gt;Our main character is Dorothe Bartleby and she’s a grad student in Magic. As we meet her, she’s flunked her first oral qualifying exam and has one more chance to pass or get kicked out.&lt;/p&gt;

&lt;p&gt;Magic, in this book, can only be done by people born on a new moon, which is a clever way to limit magic usage without making it feeling like a chosen one or eugenics element.&lt;/p&gt;

&lt;p&gt;Anyway, Dorothe is trying to prove that early novels were literally magic, and along the way she inadvertently creates a magical skull that narrates her life whether she wants it to or not. In the background – and this should be a spoiler but it’s on the dust jacket – students who ask for disability accommodations are vanishing. So, subtle like brick, but also fun?&lt;/p&gt;

&lt;p&gt;It’s trying to be both cozy and dark academia, a mix that doesn’t always work (the cozy part depends on us not fully internalizing that dozens of students have been magically kidnapped). The skull bit works better than you’d expect (or than I expected), and I was on board at the end (though I think the end doesn’t quite hold on its own logic).&lt;/p&gt;

&lt;h3 id=&quot;snake-eater-by-t-kingfisher&quot;&gt;&lt;a href=&quot;https://amzn.to/3LAZtWg&quot;&gt;Snake-Eater&lt;/a&gt; by T. Kingfisher&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Why Did I Read This:&lt;/strong&gt; Kingfisher is mostly an insta-buy&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opening Lines:&lt;/strong&gt; “Selena picked her new home for no better reason than the dog laid down on the porch.”&lt;/p&gt;

&lt;p&gt;I read a lot of Kingfisher, but they are all so good…&lt;/p&gt;

&lt;p&gt;This is Kingfisher in horror mode. Our heroine is Selena, she’s fled to the desert to escape a bad partner and visit her aunt, only to learn that her aunt has passed on. She moves into her aunt’s house and eventually discovers that the roadrunner god is real, had an interesting relationship with her aunt, and would like to continue the deal.&lt;/p&gt;

&lt;p&gt;Two quick notes:&lt;/p&gt;

&lt;p&gt;For a novel that is nominally horror, this scores really high on the Cozy Scale, like 7 or 8, might even be higher than &lt;strong&gt;Brigands &amp;amp; Breadknives&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For no obvious reason, this book takes place at least 30 years in the future, there’s a reference to a moon colony, and the desert area is a “historical zone”, which is never fully explained. I’m great with this – I actually love it when authors do subtle SF/Fantasy stuff in the background, but I wonder if it confused anybody.&lt;/p&gt;

&lt;h2 id=&quot;triple&quot;&gt;Triple&lt;/h2&gt;

&lt;h3 id=&quot;the-will-of-the-many-by-james-islington&quot;&gt;&lt;a href=&quot;https://amzn.to/4iQmfFH&quot;&gt;The Will of the Many&lt;/a&gt; by James Islington&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; A Roman Empire coded magic school&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Did I Read This:&lt;/strong&gt; Big fave on BookTok&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opening Lines:&lt;/strong&gt; “I am dangling, and it is only my fathers blood-slicked grip around my wrist that stops me from falling”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do The Math:&lt;/strong&gt; Beru Cormorant + Name of the Wind x Roman Empire + a bunch of other magic school stories&lt;/p&gt;

&lt;p&gt;I was extremely into this for about the first two-thirds.&lt;/p&gt;

&lt;p&gt;The magic system is very cool, and works as metaphor while also being interesting as not-metaphor. The main character is also interesting, and I think the book does kind of an interesting thing with the Chosen One trope, in that he’s basically explicitly set up to look like a Chosen One. There are some cool set pieces, characters that are meant to be smart are actually smart, but not infallible. It’s got a lot going on.&lt;/p&gt;

&lt;p&gt;The last quarter or so of the book is basically a boss battle. It’s the Final Task of the school year, and it’s probably overly complicated, in that the book goes into a lot of strategy mishigas that is ultimately totally irrelevant. Then our main character gets a glimpse into what the real stakes are, and the literal last five pages make the book much weirder without explanation. So, at the end of the day, there’s a lot that isn’t really resolved (apparently this is a trilogy, but my guess based on where this one ended was duology….wait, again, this information is look up able, and trilogy it is) and while the scope of the series seems epic, that’s not a totally satisfying experience on its own.&lt;/p&gt;

&lt;h3 id=&quot;the-river-has-roots-by-amal-el-mohtar&quot;&gt;&lt;a href=&quot;https://amzn.to/4b5AhS7&quot;&gt;The River Has Roots&lt;/a&gt; by Amal El-Mohtar&lt;/h3&gt;

&lt;p&gt;This is El-Mohtar’s first solo novella, she is one half of the duo responsible for &lt;em&gt;This is How You Lose The Time War&lt;/em&gt;,. This book is fantasy, and fairy fantasy at that, about two sisters one of whom falls in love with one of the fae, and one of whom doesn’t. This is the kind of fantasy story that feels like folklore, is designed to feel like folklore, plus some flat-out gorgeous writing.&lt;/p&gt;

&lt;h3 id=&quot;the-martian-contingency-by-mary-robinette-kowal&quot;&gt;&lt;a href=&quot;https://amzn.to/49KzqE8&quot;&gt;The Martian Contingency&lt;/a&gt; by Mary Robinette Kowal&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Why Did I Read This:&lt;/strong&gt; I love this series basically beyond rational thought&lt;/p&gt;

&lt;p&gt;When I say that this is objectively the weakest book in the series, it’s still going to be one of my top five or so books of the year, because, as I said, I love this series beyond rational thought.&lt;/p&gt;

&lt;p&gt;Unlike the previous three books, this one is less plot and a bit more vibes-based – all three previous books had very strong goals, this one is more of a mystery and character book. It’s great – Kowal really makes the possibility of the best minds in all the world working together look very enticing. But it’s also a little less focused, and we’re starting to get to the point where the plot is constrained by the fact that they are all prequels to the original Lady Astronaut of Mars short story (which I wonder if that makes parts of the plot feel weird given that a lot of the novel readers probably haven’t seen that story).&lt;/p&gt;

&lt;p&gt;All that said, after a book in this series that ended with Elma saying the Jewish Mourner’s Kaddish, this one ends with her saying Shehechianu, the Jewish prayer of thanks, said in response to happy days. And, well Elma is pretty high on my list of fictional Jewish characters who seem to relate to Judaism the way that I do. It hit.&lt;/p&gt;

&lt;p&gt;I should note there’s a thing here… Kowal admitted online using an early version of LLM (from, like 2022) to help convert real news articles to the fake news articles and to help narrow down the questions she was asking experts. This was before LLMs really became a flashpoint, and it was plausible to just putter around. Anyway, Kowal says there are maybe like 50 words in the book that come from an LLM. I don’t really care, personally, but it does seem like this will knock the book out of Nebula consideration.&lt;/p&gt;

&lt;h3 id=&quot;a-drop-of-corruption-by-robert-jackson-bennett&quot;&gt;&lt;a href=&quot;https://amzn.to/3YIb1dd&quot;&gt;A Drop of Corruption&lt;/a&gt; by Robert Jackson Bennett&lt;/h3&gt;

&lt;p&gt;I read this back to back with &lt;em&gt;The Spellshop&lt;/em&gt; and that’s quite a contrast…&lt;/p&gt;

&lt;p&gt;Book two in what is, I guess, the “Ana and Din Mysteries” series. If anything, it might be a touch better than book one.&lt;/p&gt;

&lt;p&gt;Fantasy series, an empire beset by magical titans, whose blood transforms the land. The empire has industrialized and weaponized the titan blood to create all manner of things, including, for example, Din, a person with perfect recall.&lt;/p&gt;

&lt;p&gt;In this book, we’ve moved to the city that is the main refinery for titan blood, which is located an a great geographic location and a tricky political one, in that it is technically not in the empire, but in a neighboring kingdom. There’s a murder. Locked room. And a conspiracy. And eventually a story about what it means to have power, what it means to have responsibility, and what it means to be part of an empire that is so clearly made for a single purpose.&lt;/p&gt;

&lt;p&gt;I’m not quite sure how Bennett does it, but the atmosphere of the book is practically tangible, you can touch it and smell it and practically hear the ground squish when the characters walk in the mud and wow I love this series.&lt;/p&gt;

&lt;h3 id=&quot;when-we-were-real-by-daryl-gregory&quot;&gt;&lt;a href=&quot;https://amzn.to/44K2nhN&quot;&gt;When We Were Real&lt;/a&gt; by Daryl Gregory&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; What if we were living in a simulation. I mean, what if we all, 100% knew for sure we were in a simulation?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Did I Read This:&lt;/strong&gt; Gregory is the author of the amazing Spoonbenders.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opening Lines:&lt;/strong&gt; “…and there’s the tour bus now, green and bulbous, with long black mirrors drooping in front of its face like caterpillar antennae. It’s climbing out of the underworks of Manhattan, onto the upper level of the George Washington Bridge. CANTERBURY TRAILS is written across its side in white script.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do The Math:&lt;/strong&gt; Canterbury Tales + The Good Place&lt;/p&gt;

&lt;p&gt;I feel like this one didn’t get enough attention.&lt;/p&gt;

&lt;p&gt;Everybody lives in a simulation. How do they know that? Well, starting seven years ago, once a week everybody in the world wakes up to see the words “you are living in a simulation” floating in front of them. Also, there are dozens and dozens of weird anomalies in the world, places where the laws of physics don’t apply. So people are pretty much convinced.&lt;/p&gt;

&lt;p&gt;This book is unhinged in the best way.&lt;/p&gt;

&lt;p&gt;Our main characters are a group of people who have gathered for a bus tour of several of the anomalies in the USA. (The bus company is Canterbury Trails, in case you had any confusion about what was happening…) Part of the fun of the book is how weird and inventive the anomalies are, and I won’t spoil it.&lt;/p&gt;

&lt;p&gt;Part of the depth of the book is the range of reactions to things like what does morality mean if we are in a simulation? Do you believe in god or do you pray to the simulators? When did the simulation start? Are all the people in the simulation sentient?&lt;/p&gt;

&lt;p&gt;There are also characters. A comic book writer and his terminally ill friend. A nun, with a Rabbi. A 17 year old influencer. A skeptic. The character arcs are interesting, especially the range of how they engage with the simulation.&lt;/p&gt;

&lt;p&gt;This is a classic SF metaphor but not a metaphor book, and I’ve been thinking about it for weeks.&lt;/p&gt;

&lt;h3 id=&quot;the-incandescent-by-emily-tesh&quot;&gt;&lt;a href=&quot;https://amzn.to/45XU1Dy&quot;&gt;The Incandescent&lt;/a&gt; by Emily Tesh&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; I just knew this one would have a real humdinger of a marketing pitch, and lo: “A Deadly Education meets Plain Bad Heroines”. So, I guess I’ll take fictional boarding schools and death for $200, Ken.&lt;/p&gt;

&lt;p&gt;What it really is, is magic school from the point of view of the administrators.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Did I Read This:&lt;/strong&gt; Very strong prepublication buzz, and I loved Tesh’s last book.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opening Lines:&lt;/strong&gt; “Doctor Walden looked glumly at the form she had to fill in. At the top it said RISK ASSESSMENT.”&lt;/p&gt;

&lt;p&gt;Our lead character is one of the most powerful wizards in the world, who also is the head teacher at one of the most prestigious magical boarding schools in the world. Magic is not a secret in this world, but it’s dangerous and not very powerful, so even a prestigious magical boarding school is always looking at the bottom line.&lt;/p&gt;

&lt;p&gt;Turns out that a boarding school with hundreds of young magicians attracts powerful demons (I guess that’s the &lt;em&gt;Deadly Education&lt;/em&gt; hook), and right at the beginning of the book a very powerful demon that has been harassing the school for years is killed by our main character.&lt;/p&gt;

&lt;p&gt;Wouldn’t that normally be the end of a book? You’d think, but in this case, the real danger is what happens next. There’s a student death from the past that lingers in the history of the school, which I guess is the &lt;em&gt;Plain Bad Heroines&lt;/em&gt; hook. Then, well, then other things happen.&lt;/p&gt;

&lt;p&gt;I loved this book, it’s unabashedly a love note to teachers, while being very skeptical of academia. The plot structure is a little weird (also true of Tesh’s last novel), but ultimately works. It’s very creepy when it needs to be, there’s a cute love story there, but ultimately it’s about what a teacher would try to protect, and also about continuing to live after making terrible mistakes.&lt;/p&gt;

&lt;h3 id=&quot;katabasis-by-r-f-kuang&quot;&gt;&lt;a href=&quot;https://amzn.to/4pWxqPe&quot;&gt;Katabasis&lt;/a&gt; by R. F. Kuang&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; The official marketing comps are “Dante’s Inferno meets Susanna Clarke’s Piranesi”, which I admit does paint a picture. I guess if you squint &lt;em&gt;Piranesi&lt;/em&gt; is dark academia? Otherwise not really sure what it’s doing in the mix, the books aren’t tonally similar at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Did I Read This:&lt;/strong&gt; Boy this got some weird online discourse….&lt;/p&gt;

&lt;p&gt;First off, per an interview, Kuang pronounces it “kuh TAB uh sis”, and not “CAT a basis” but also said she doesn’t really care how you pronounce it.&lt;/p&gt;

&lt;p&gt;Alice Law literally Goes To Hell after her advisor dies in a freak lab accident that she feels responsible for. She’s accompanied by her frenemy Peter, in a world where Magic is real and where Dante and the others are non-fiction.&lt;/p&gt;

&lt;p&gt;This is one of those buzzy internet books that I feel like I’ve been hearing takes on for months. I don’t really want to get into all the takes, but I do want to say a few things.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;The magic, environment, and character arcs are all thematically resonant with each other in a way that is really rare in magical fantasy. The magic explicitly relies on delusion, Alice is a character defined largely by her self-delusions, and her trip into Hell takes her to characters basically trapped by their self delusions. When they all align, the book is very satisfying.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I’d like to introduce a trope named “Lipstick Incident”. A Lipstick Incident is something that happened in the past that caused two people to become estranged, and which is referred to repeatedly before actually being explained. &lt;em&gt;Katabasis&lt;/em&gt; is a little bit of a fake out here – Alice refers repeatedly to something that caused her and Peter to no longer be close, but the eventual reveal, while bad, is kind of dwarfed by two other reveals that we don’t see coming because Kuang has been kind of distracting us with the Lipstick Incident. (Fine, I’m grabbing the term from the brief period where Mark Waid got to redo all of Archie comics, look it up, he really had a good thing going there until Riverdale came out and swamped him.)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I’d like to introduce a trope “obvious misunderstanding” – this one you can probably guess. Alice has an obvious misunderstanding with Peter about his notebook, this is, to me, one of the book’s most unforced errors. (The other, I think, is that the villains are underdeveloped) I do like that Kuang takes a big swing at about the two-thirds mark. And that we don’t learn Alice’s real motivation for a long time.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;There was some online griping about the amount of sources that Kuang uses in the book, that it felt like a textbook or something. Worked for me, but it is a book that puts the “academia” in “dark academia”.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Triple. Wasn’t sure it would be. Is.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;lessons-in-magic-and-disaster-by-charlie-jane-anders&quot;&gt;&lt;a href=&quot;https://amzn.to/4sNtgLW&quot;&gt;Lessons in Magic and Disaster&lt;/a&gt; by Charlie Jane Anders&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; Oh, this got a fun one: “In the vein of Alice Hoffman and Charlie Jane Anders’s own All the Birds in the Sky” – you don’t often see an author comped to herself. Alice Hoffman here is presumably the Practical Magic books? To me the comp here is Jo Walton’s &lt;em&gt;Among Others&lt;/em&gt;, which had a similar magic system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opening Lines:&lt;/strong&gt; “Jamie has never known what to say to her mother. And now—when it matters most of all, when she’s on a rescue mission—she knows even less.”&lt;/p&gt;

&lt;p&gt;This was a roller coaster, at least for me.&lt;/p&gt;

&lt;p&gt;I had been putting it off on fear that it would be too heavy. And not too heavy, but pretty heavy, for reasons I don’t need to get into.&lt;/p&gt;

&lt;p&gt;We’ve got three stories here:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;The main story, featuring Jamie, trying to get her mom back in the world after six years of mourning for her mom’s wife. Jamie can do magic, and tries to teach her mom.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;A background thread featuring the entire history of Jamie’s parents’ relationship&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;A background thread involving the 18th Century woman authors that Jamie is researching for her dissertation – Anders clearly loves the research she’s done for this part, and it’s hard to blame her&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anyway, there’s a lot here to like. I’m fond of a magic system where you aren’t quite sure if magic is real. For a system without explicit rules, Anders does a great job making clear what happens at the end, and why. Like Katabasis, a very good case of the magic system reinforcing the themes of the book.&lt;/p&gt;

&lt;p&gt;So, I was into this at the end, parts of which are lovely.&lt;/p&gt;

&lt;p&gt;My main complaints:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;At the end, the characters are really didactic in explaining the point of the story&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I found it hard to read in long chunks, especially in the middle, in part because it’s so raw, so this isn’t really a complaint.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I guess I was sure this was a single for a while, but when I finished it, my brain said “triple”. But I’m still thinking about it – I’m guessing this is going to be a book where I remember the best parts and not the flaws.&lt;/p&gt;

&lt;h3 id=&quot;the-cartoonists-club-by-raina-telgemeier-and-scott-mccloud&quot;&gt;&lt;a href=&quot;https://amzn.to/45LGWxa&quot;&gt;The Cartoonists Club&lt;/a&gt; by Raina Telgemeier and Scott McCloud&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; It’s the middle grade Understanding Comics&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Did I Read This:&lt;/strong&gt; Did you see where I said it’s the middle-grade Understanding Comics?&lt;/p&gt;

&lt;p&gt;My love for the work of Scott McCloud goes back a long way, before &lt;em&gt;Understanding Comics&lt;/em&gt;. I deeply wish that the &lt;em&gt;Zot!&lt;/em&gt; omnibus books would get a digital release. That’s neither here nor there, I suppose. This is explicitly an attempt to do a narrative middle-grade gloss on &lt;em&gt;Understanding Comics&lt;/em&gt;, a few kids get together in a cartoonist club, and a friendly librarian teaches them some of the lessons of &lt;em&gt;Understanding Comics&lt;/em&gt; in a kid-friendly way. Sounds potentially dull, but these are two genuine masters here, so it’s actually quite charming and fun.&lt;/p&gt;

&lt;h3 id=&quot;dead-hand-rule-by-max-gladstone&quot;&gt;&lt;a href=&quot;https://amzn.to/4sNnXfC&quot;&gt;Dead Hand Rule&lt;/a&gt; by Max Gladstone&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Why Did I Read This:&lt;/strong&gt; Craft Wars!&lt;/p&gt;

&lt;p&gt;Book Nine of Ten for the Craft Sequence, this was supposed to the ending book, but Gladstone wound up splitting it.&lt;/p&gt;

&lt;p&gt;I do not recommend you start with this one. The Craft Sequence takes place in a fantasy world where magic is essentially based on law and finance. Except for the gods. And the monsters, I guess. There’s a lot, and it’s fun to see a fantasy world with basically modern tech and wild baroque imagination.&lt;/p&gt;

&lt;p&gt;At the end of book 8, we had a situation where there is an overwhelming external force poised to destroy the world, and also an internal force that is, at the very least, determined to knock down any power that could stop the outsiders.&lt;/p&gt;

&lt;p&gt;What do the existing powers do? They call a conclave. All the powerful people and nations mentioned or alluded to in the past books all come to the metaphorical table in our main city of Alt Coulomb to decide how to work together to defeat the foes. But who isn’t at the table? And who isn’t really interested in working together?&lt;/p&gt;

&lt;p&gt;The first, like, two thirds of this book are really fun. It’s a bunch of big personalities having big debates against high stakes, with the occasional assassination attempt to liven things up. It’s maybe the loosest Gladwell’s writing has ever been – parts of it are the most Pratchetty this series have ever been, just with more evil squid overlords.&lt;/p&gt;

&lt;p&gt;Then things start happening, which is also fun, but starts to run up against the limits of how to make the magic in these books comprehensible to the readers. Still, great book. Ends on a cliff.&lt;/p&gt;

&lt;h3 id=&quot;the-essential-peanuts-by-mark-evanier&quot;&gt;&lt;a href=&quot;https://amzn.to/49GKRMT&quot;&gt;The Essential Peanuts&lt;/a&gt; by Mark Evanier&lt;/h3&gt;

&lt;p&gt;This one is pretty straightforward, a history of &lt;em&gt;Peanuts&lt;/em&gt; through 75 “essential” strips, each one augmented with a selection of strips on the same theme, and a bunch of short essays covering the history of the strip from Schulz’ childhood until his death and the continued life of the characters. Picks its spot, does the thing quite well, gets off the stage.&lt;/p&gt;

&lt;h3 id=&quot;slayers-of-old-by-jim-c-hines&quot;&gt;&lt;a href=&quot;https://amzn.to/4jVyfX6&quot;&gt;Slayers of Old&lt;/a&gt; by Jim C. Hines&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; “Buffy the Vampire Slayer meets Golden Girls” – maybe the easiest one of these on the board. That’s pretty much exactly it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opening Lines:&lt;/strong&gt; “Forty years ago, I began keeping a list of annoyances that came with being a Hunter of Artemis. I’d gotten up to two hundred and four.”&lt;/p&gt;

&lt;p&gt;Our heroes are, not to put a point on it, old. Jenny is a former Hunter of Artemis, which is Legally Distinct from being a Vampire Slayer. She’s Buffy, age 55, if Buffy had rage quit at age 20 and told the Watchers to pound sand. Annette is a little older, she’s a half-succubus former supernatural PI. Temple is near 100, he’s a magical protector, think Dr. Strange at age 100. Together they run a bookshop in Salem MA, and do not fight crime.&lt;/p&gt;

&lt;p&gt;Until… Look, you think this book is going to be light and filled with jokes like staking a vampire with a cane while saying get off my lawn (my joke, not Hines’), and there’s some of that. Hines does a particularly good job with Jenny’s back story, it really feels like there’s a seven-season TV show back there.&lt;/p&gt;

&lt;p&gt;But Hines is not afraid for the book to get a little dark. Jenny is dealing with real trauma from her time as a hero, Annette genuinely regrets some life choices. Temple is really aging and can’t do all the things he once could.&lt;/p&gt;

&lt;p&gt;The eventual Big Bad is tied to one of those things, and I’m dying to describe it here because the Buffy fans will eat it up but it’s spoilers.&lt;/p&gt;

&lt;p&gt;So the book is fun, deeper than you’d expect, nails the ending – I was reluctant to score it a triple because it’s kind of light, but really, what’s a triple if not those things.&lt;/p&gt;

&lt;p&gt;Trope note, this book does the thing where there are short interstitial bits between chapters that are uncredited, but we become aware they are the Big Bad POV. I like this structure, I’m not sure how many other SF/F books I’ve seen it in (Ali Hazelwood uses it in a couple of her romance books.)&lt;/p&gt;

&lt;h3 id=&quot;mark-twain-by-ron-chernow&quot;&gt;&lt;a href=&quot;https://amzn.to/4bETLgu&quot;&gt;Mark Twain&lt;/a&gt; by Ron Chernow&lt;/h3&gt;

&lt;p&gt;One of the problems with, like, the StoryGraph tracking and stuff is that it subtly pushes me away from long books.&lt;/p&gt;

&lt;p&gt;And, so, I embarked upon Ron Chernow’s 1100 page biography of Mark Twain, which became my personality for a couple weeks in December 2025.&lt;/p&gt;

&lt;p&gt;It’s hard to really review a biography, here are some thoughts. (I will note that the professional reviews of this book are pretty mixed)&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;The sheer breadth of Twain’s life is astonishing. He was wildly famous in his time, and almost absurdly well-traveled, and as a result, has some connection with nearly every person you can think of who was prominent in the US or Europe between 1870 and 1910. At least six US Presidents pop in. At one point, we go back to back between Helen Keller (Twain was the first to call Anne Sullivan a “miracle worker”) and Nikolai Tesla.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;What people struggle with in this book is the length – the body of the book is 1050 pages. And the proportions. Huck Finn is published on page 340. The last 15 years of Twain’s life are fully half the book. At times, it reads like a biography of his daughters.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I can’t speak to what this book brings to the table in terms of new scholarship. There are critiques that it doesn’t really get Twain’s irreverence. I kind of see that? But also I kind of don’t.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;It’s fun to think about what Twain would be in today’s media environment – he was made for the blog/newsletter/posting world. But he’d also either be a victim of wellness grifters or an actual wellness grifter…&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;As I’ve said before, when I really lock in to a book, I often want to read it aloud. Well, not Chernow’s book, but Twain. Specifically &lt;a href=&quot;https://warprayer.org&quot;&gt;“The War Prayer”&lt;/a&gt;, a short-story/essay published after Twain died of which I was unaware and which could have been written yesterday.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;home-run&quot;&gt;Home Run&lt;/h2&gt;

&lt;h3 id=&quot;the-raven-scholar-by-antonia-hodgson&quot;&gt;&lt;a href=&quot;https://amzn.to/49k4z10&quot;&gt;The Raven Scholar&lt;/a&gt; by Antonia Hodgson&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; Just… trust me and read it?  (Interestingly, it has a very prominent blurb from Alex E. Harrow, see next book)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Did I Read This:&lt;/strong&gt; A &lt;em&gt;lot&lt;/em&gt; of positive buzz online&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opening Lines:&lt;/strong&gt; “Once they made sacrifices here, to appease the Eight. This was many thousands of years ago, but the rock remembers.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommended If You Like:&lt;/strong&gt; Non-hereditary empires, genuinely funny gods, tangled plots, ravens, weapons-grade doorstoppers&lt;/p&gt;

&lt;p&gt;I saw a review of this that was basically “this is a book that, while you are reading it, you think ‘this is a good book’”.&lt;/p&gt;

&lt;p&gt;That sounds vague, I guess, but I completely get it.&lt;/p&gt;

&lt;p&gt;This is a good book.&lt;/p&gt;

&lt;p&gt;It’s a 650 page book that doesn’t feel long.&lt;/p&gt;

&lt;p&gt;It’s a novel full of twists that all feel earned, I was genuinely shocked by a twist I didn’t at all see coming, which is rare.&lt;/p&gt;

&lt;p&gt;It’s a book that keeps changing what’s going on – it’s a mystery, no it’s a trial, no it’s survival.&lt;/p&gt;

&lt;p&gt;It’s a book with smart characters who are actually smart and plans that are actually well planned.&lt;/p&gt;

&lt;p&gt;Also it’s funny and has a very unique narrative voice.&lt;/p&gt;

&lt;p&gt;My only quibble is that it briefly threatens to get out of the author’s control in the last, say, 20% and that also a lot of the end of the book is setting people in place for book two, which kind of muffles the book from having a satisfying end on its own.&lt;/p&gt;

&lt;p&gt;But, all that said, this is a very good book and if you at all like 650 page fantasy novels, you will probably like this.&lt;/p&gt;

&lt;h2 id=&quot;grand-slam&quot;&gt;Grand Slam&lt;/h2&gt;

&lt;h3 id=&quot;the-everlasting-by-alex-e-harrow&quot;&gt;&lt;a href=&quot;https://amzn.to/4pfvK2P&quot;&gt;The Everlasting&lt;/a&gt; by Alex E. Harrow&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; What if Replay was about Red Sonja?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Did I Read This:&lt;/strong&gt; Harrow is a little hit or miss for me, but this book had outstanding buzz.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opening Lines:&lt;/strong&gt; “Several years after the war, during the mid-afternoon hour I generally put aside to fantasize about setting fire to my manuscript and disappearing into the countryside to raise goats, I received a book in the post.”&lt;/p&gt;

&lt;p&gt;So about five pages into this book, I thought, this book is a triple or a home run depending on if it sticks the landing. Five pages!&lt;/p&gt;

&lt;p&gt;Oh, it stuck the landing.&lt;/p&gt;

&lt;p&gt;Our lead is Owen, he’s a scholar in a secondary world, modern age, studying Una Everlasting, a legendary hero. As the book starts, he receives a miracle in the mail. The miracle is a book, &lt;em&gt;The Death of Una Everlasting&lt;/em&gt;, believed to be a myth. He sets about to translate it, and through some machinations, he meets with the ambitious Minister of War, and through other machinations, is sent back in time where he meets Una Everlasting herself.&lt;/p&gt;

&lt;p&gt;That sounds contrived, you think. Buddy, you don’t know the half of it.&lt;/p&gt;

&lt;p&gt;You think you know what will happen next. And kind of you do, but then at a critical moment, when Owen says he’ll never forget Una, his antagonist says, “you always do”.&lt;/p&gt;

&lt;p&gt;Oh, you think, this isn’t a time travel story, it’s a time loop story.&lt;/p&gt;

&lt;p&gt;And you think you know what will happen next. And kind of you do.&lt;/p&gt;

&lt;p&gt;And mostly you don’t.&lt;/p&gt;

&lt;p&gt;This book is so good on so many levels, I want to start with, the writing is, sentence by sentence, &lt;em&gt;gorgeous&lt;/em&gt;. So many beautiful turns of phrase, and smart observations.&lt;/p&gt;

&lt;p&gt;The plot is complex but comes together like the tumblers of a safe falling into place. You don’t think it’s going to be the kind of book where everything falls in line, but it is.&lt;/p&gt;

&lt;p&gt;What Harrow does essentially in the background while we’re paying attention to Owen and Una is astonishing. What she does after she pulls the curtain aside is amazing, and what happens after that is heartbreaking.&lt;/p&gt;

&lt;p&gt;It’s a romance, sure, but it’s also about stories, and what we fight for, what we fight for as people and what we fight for as countries and what makes a story something people will fight for. It’s an angry book, in places, it’s a deeply romantic book in places, it’s even funny here and there.&lt;/p&gt;

&lt;p&gt;There’s so much &lt;em&gt;craft&lt;/em&gt; here, in the service of so much to say. I think it pipped &lt;em&gt;Raven Scholar&lt;/em&gt; for the win here at the very end of the year.&lt;/p&gt;</content><author><name>Noel Rappin</name></author><category term="books" /></entry><entry><title type="html">Ruby And Its Neighbors: Lisp</title><link href="https://noelrappin.com/2025/11/ruby-and-its-neighbors-lisp/" rel="alternate" type="text/html" title="Ruby And Its Neighbors: Lisp" /><published>2025-11-24T00:00:00+00:00</published><updated>2025-11-24T00:00:00+00:00</updated><id>repo://blog.collection/_blog/2025-11-24-ruby-and-its-neighbors-lisp.md</id><content type="html" xml:base="https://noelrappin.com/2025/11/ruby-and-its-neighbors-lisp/">&lt;p&gt;So, after writing two articles basically assuming what Ruby’s influence are, it occurred to me to check &lt;a href=&quot;https://www.ruby-lang.org/en/about/&quot;&gt;the About Ruby page on the official Ruby site&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It says this:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Ruby is a language of careful balance. Its creator, &lt;a href=&quot;http://www.rubyist.net/~matz/&quot;&gt;Yukihiro “Matz” Matsumoto&lt;/a&gt;, blended parts of his favorite languages (Perl, Smalltalk, Eiffel, Ada, and Lisp) to form a new language that balanced functional programming with imperative programming.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We’ve already talked about &lt;a href=&quot;https://noelrappin.com/blog/2025/10/ruby-and-its-neighbors-perl/&quot;&gt;Perl&lt;/a&gt; and &lt;a href=&quot;https://noelrappin.com/blog/2025/11/ruby-and-its-neighbors-smalltalk/&quot;&gt;Smalltalk&lt;/a&gt;. I don’t know much about Eiffel or Ada, though I assume Eiffel inspired some of Ruby’s object structure somehow. Ada is perhaps the most statically typed language in existence and it’s hard to see how it could have possibly influenced Ruby in any significant way, but I guess it must have.&lt;/p&gt;

&lt;p&gt;Which brings us to Lisp. I’ve been a little nervous about approaching Lisp because, while I have actually done projects in Lisp, it’s been a while. And I assume there’s a whole cadre of Lisp-knowers waiting to jump on misstatements. Hi, Lisp-knowers!&lt;/p&gt;

&lt;p&gt;I don’t even really know where to start… Let’s start with Lisp’s most prominent syntactic feature… If you know anything about Lisp, it’s probably all the parentheses.&lt;/p&gt;

&lt;p&gt;Lisp has a very basic syntax, or at least the core of Lisp does. There’s no syntactic distinction between code and data.&lt;/p&gt;

&lt;p&gt;Lisp code and Lisp data are made up of &lt;em&gt;lists&lt;/em&gt;. A list is surrounded by parentheses and contains multiple &lt;em&gt;atoms&lt;/em&gt;, separated by a space. Lists can be nested.&lt;/p&gt;

&lt;div class=&quot;language-lisp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(Once upon a time, I was told that you could always tell Lisp programmers during the push card days, because they’d have shirt pockets filled with right-parenthesis cards. Is this true? Probably not, but I love the story.)&lt;/p&gt;

&lt;p&gt;Internally, the list is stored as a set of connected pairs. For the top list, the first pair is made of the atom &lt;code class=&quot;highlighter-rouge&quot;&gt;1&lt;/code&gt; and a link to the next pair, which is made up of the atom &lt;code class=&quot;highlighter-rouge&quot;&gt;2&lt;/code&gt; and a link to the next pair, and so on. Many languages have this structure under the name &lt;em&gt;linked list&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;For historical reasons, the first element of the list was often called the &lt;code class=&quot;highlighter-rouge&quot;&gt;car&lt;/code&gt; and the remainder of the list is called the &lt;code class=&quot;highlighter-rouge&quot;&gt;cdr&lt;/code&gt;, which is pronounced something like “could-er”. (More modern Lisps call these &lt;code class=&quot;highlighter-rouge&quot;&gt;first&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;rest&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Okay, fine – &lt;code class=&quot;highlighter-rouge&quot;&gt;car&lt;/code&gt; stands for something something “contents”, “address” and “register” and &lt;code class=&quot;highlighter-rouge&quot;&gt;cdr&lt;/code&gt; similarly has to do with “contents”, “decrement” “register”, which are terms that would mean something if you programmed in assembly on an IBM 704 in, like, 1959. (Wikipedia just told me they aren’t simple acronyms, which is news to me and every other source I’ve learned Lisp from in the last thirty years.)&lt;/p&gt;

&lt;p&gt;There are at least three strands of Lisp that are even a little viable today:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://lisp-lang.org&quot;&gt;Common Lisp&lt;/a&gt;, which is a direct descendent of the original Lisp from the 60s, and came out of standardization attempts in the 1980s. Common Lisp has mostly a basic syntax, but a very large number of standard functions and special cases.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://www.scheme.org&quot;&gt;Scheme&lt;/a&gt;, which was created in the 1980s as an alternative to Common Lisp, with simpler internals and fewer special cases. Scheme is notable as the programming language in the original version of the book &lt;em&gt;Structure and Interpretation of Computer Programs&lt;/em&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://clojure.org&quot;&gt;Clojure&lt;/a&gt;, which is a Java VM based Lisp that originally released in 2007. It adds some extra syntax for different kinds of literal data. In particular, Clojure introduces an array-like list rather than a linked list-like list, this improves performance at the cost of making the syntax a little more complex.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All these are slightly different, and in particular they have slightly different syntaxes for very basic things, but we’ll muddle through.&lt;/p&gt;

&lt;p&gt;Anyway, lists. In Lisp, lists are both data and code. In order to convert code to data you have to evaluate it. Lisp was (I think) the first language to be interactive with a REPL and you can get Lisp to evaluate your code in that REPL – this example is Clojure, which was the least-sketchy install on a modern Mac, but you can also use &lt;a href=&quot;https://try.scheme.org&quot;&gt;https://try.scheme.org&lt;/a&gt; to play around with Scheme:&lt;/p&gt;

&lt;div class=&quot;language-clojure highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;user=&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user=&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user=&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;19&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user=&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In general, what happens here is that, when treated as code, the first element of a list is expected to be something function-like that can be applied to the rest of the elements of the list, so &lt;code class=&quot;highlighter-rouge&quot;&gt;+&lt;/code&gt; adds the elements of the list. Nested lists are evaluated first.&lt;/p&gt;

&lt;p&gt;That last example is doing something subtle with that quote character. Quoting was, I am now remembering, the bane of my existence when I was actually using Lisp. The quote character is a shortcut for an actual function named &lt;code class=&quot;highlighter-rouge&quot;&gt;quote&lt;/code&gt; and what it means is “treat me as data, not as code”. So, in that last example, the function &lt;code class=&quot;highlighter-rouge&quot;&gt;first&lt;/code&gt; is getting the list as a whole as an argument. Without the quote, the system would attempt to evaluate &lt;code class=&quot;highlighter-rouge&quot;&gt;(1 2 3 4)&lt;/code&gt; and error because &lt;code class=&quot;highlighter-rouge&quot;&gt;1&lt;/code&gt; isn’t function-like. (Not for nothing, but the clojure REPL error message in that case is really unhelpful).&lt;/p&gt;

&lt;p&gt;What’s happening here internally is that the system is calling a function named &lt;code class=&quot;highlighter-rouge&quot;&gt;eval&lt;/code&gt; with the list as an argument. The &lt;code class=&quot;highlighter-rouge&quot;&gt;eval&lt;/code&gt; function looks at the first element of its argument and determines what to do based on that – most commonly the first element is the name of a function, in which case the function is executed using a method called &lt;code class=&quot;highlighter-rouge&quot;&gt;apply&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Where this gets subtle is that these two expressions have the same result:&lt;/p&gt;

&lt;div class=&quot;language-clojure highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;eval&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;eval&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But they are taking different paths. The top expression starts by evaluating the inner list with &lt;code class=&quot;highlighter-rouge&quot;&gt;first&lt;/code&gt; and so it boils down to &lt;code class=&quot;highlighter-rouge&quot;&gt;(eval 1)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The bottom expression actually takes the entire list as data, so it actually calls &lt;code class=&quot;highlighter-rouge&quot;&gt;eval&lt;/code&gt; on &lt;code class=&quot;highlighter-rouge&quot;&gt;(first &apos;(1 2 3 4))&lt;/code&gt; which also boils down to &lt;code class=&quot;highlighter-rouge&quot;&gt;1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If I had fully understood this subtlety in, like 1990, my life would have been somewhat easier.&lt;/p&gt;

&lt;p&gt;Instead of a function, the first element of a list could be something typically called a “special form”. A special form does something weird, it has atypical evaluation or a side effect or both. For example, all these Lisps have a special form for assignment and a special form for &lt;code class=&quot;highlighter-rouge&quot;&gt;if&lt;/code&gt;. Here’s Clojure:&lt;/p&gt;

&lt;div class=&quot;language-clojure highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; 
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;%s is less than 5&quot;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; 
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;%s is not less than 5&quot;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So, &lt;code class=&quot;highlighter-rouge&quot;&gt;def&lt;/code&gt; is a special form because it has a side effect, it’s creating a local value that can be used later on, so it’s not evaluating &lt;code class=&quot;highlighter-rouge&quot;&gt;x&lt;/code&gt;. And &lt;code class=&quot;highlighter-rouge&quot;&gt;if&lt;/code&gt; is a special form because the format is &lt;code class=&quot;highlighter-rouge&quot;&gt;if condition true false&lt;/code&gt; and it only will evaluate whichever one of those corresponds to the condition, the other branch will not be evaluated.&lt;/p&gt;

&lt;p&gt;It’s my recollection of normal Lisp style that end parens are placed on the same line, rather than one to a line as we might do in Ruby. I also note that Lisp and Smalltalk are both languages where it was very common to work in non-monospaced fonts.&lt;/p&gt;

&lt;h2 id=&quot;history-and-lisp&quot;&gt;History and Lisp&lt;/h2&gt;

&lt;p&gt;Lisp is one of the oldest continually used programming languages going. Development on it started in the late 1950s, though in the beginning the parenthesized form was only one of the ways you could define expressions, that form quickly became the preferred, and then the only way.&lt;/p&gt;

&lt;p&gt;I’m actually a little vague on what the Lisp world was like through the 60s and 70s. My &lt;a href=&quot;https://noelrappin.com/blog/2025/07/programming-proverbs-in-1975-and-2025/&quot;&gt;programming book from 1975&lt;/a&gt; mentions Lisp a couple of times in passing, but I don’t get the sense it was heavily used outside of academia during that time. Douglas Hofstadter mentions Lisp in &lt;a href=&quot;https://amzn.to/4ilzIVQ&quot;&gt;&lt;em&gt;Goëdel, Escher, Bach&lt;/em&gt;&lt;/a&gt;, published in 1979, and wrote some columns about it in 1983 for &lt;a href=&quot;https://amzn.to/3XATraB&quot;&gt;Scientific American&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Within academia, Lisp became the primary language for Artificial Intelligence research, largely on the theory that there was something profound about blurring the line between data and code. The idea was that you could create a program that could manipulate itself. Lisp remained the primary language of AI at least through the mid 1990s and probably further than that.&lt;/p&gt;

&lt;p&gt;Lisp also became very closely identified with the early Free Software stuff, which lives on in Emacs Lisp being the configuration language of Emacs. And also in the way in which Common Lisp and Scheme are still distributed.&lt;/p&gt;

&lt;p&gt;I also get the sense that in the 70s everybody and their postdocs had their own dialect of Lisp, and there was some not-fruitful chaos there. In the early 80s, work began on a common standard, and in 1984, &lt;em&gt;Common Lisp the Language&lt;/em&gt; by Guy Steele was published, and that eventually became the basis for an ANSI standard Lisp.&lt;/p&gt;

&lt;p&gt;Also in the 80s you had Lisp Machines, full-on hardware that used Lisp as their main language the way that Unix used C. It was possible to have Lisp implementation that were as fast as C implementations (or so I have read), though I suspect the Lisp hardware was more expensive.&lt;/p&gt;

&lt;p&gt;(Here’s trivia for you – the company that made those machines was called Symbolics, and in March 1985, they became the first company to have an internet &lt;code class=&quot;highlighter-rouge&quot;&gt;.com&lt;/code&gt; domain.)&lt;/p&gt;

&lt;p&gt;I actually touched one of those Symbolics machines, there were still one or two of them hiding in the AI lab at Georgia Tech when I started, but they were pretty old, and I never did anything substantive on them.&lt;/p&gt;

&lt;p&gt;Anyway, Lisp sort of slowly became less relevant to AI research and I don’t think it ever had much commercial pull. There was a really good Mac implementation in the System 7 era that was both quite fast and tied into the Mac toolkit in a way that made it possible to build real Mac-native software. (It came with its own editor called FRED, which stood for “Fred Resembles EMACS Deliberately”, because of course it did.)&lt;/p&gt;

&lt;p&gt;Paul Graham of Y Combinator fame was a Lisp guy in his past, and around 2001 or so was trying to push &lt;a href=&quot;https://paulgraham.com/avg.html&quot;&gt;Lisp as a killer language for startups&lt;/a&gt;. Not many people took him up on that.&lt;/p&gt;

&lt;p&gt;Eventually, Clojure was released, and that did get some attention, and still survives to this day.&lt;/p&gt;

&lt;h2 id=&quot;me-and-lisp&quot;&gt;Me and Lisp&lt;/h2&gt;

&lt;p&gt;My time with Lisp was brief but meaningful. I first encountered a Lisp in the form of Scheme in my CS1 class, which used the famous &lt;a href=&quot;https://amzn.to/3XMUNPh&quot;&gt;&lt;em&gt;Structure and Interpretation of Computer Programs&lt;/em&gt;&lt;/a&gt; as its text book. I was a kid who had mostly used Turbo Pascal. To say I was unprepared was an understatement.&lt;/p&gt;

&lt;p&gt;It took me quite a while to get my head around Scheme and around functional programming generally.&lt;/p&gt;

&lt;p&gt;A few years later, as a grad student, I spent some time working with Lisp in AI classes. (I was a TA for an undergrad AI class where, due to some schedule weirdness a significant number of people had not taken a Lisp intro course and we had to run an emergency catch up session). I also built a pretty decent sized project in Mac Common Lisp, which was both fun to work in and maybe the worst codebase I ever wrote. (I still didn’t understand objects, and the Common Lisp Object Model was not helping).&lt;/p&gt;

&lt;h2 id=&quot;you-and-lisp&quot;&gt;You and Lisp&lt;/h2&gt;

&lt;p&gt;I’ve been trying in this series to give you a sense of what it feels like to work in these languages.&lt;/p&gt;

&lt;p&gt;One key thing about Lisp that I didn’t mention up above is that a key part of working in complex Lisp projects is the &lt;em&gt;macro&lt;/em&gt;. To wildly oversimplify something that I barely understood when I was using Lisp regularly, a Lisp macro takes advantage of the permeable border between Lisp code and Lisp data. A macro is a piece of code that takes data which is not quite executable code and transforms it into differently shaped data which is executable code. By which I mean the macro takes a list, does something to it and literally returns a new list that is meant to be executable.&lt;/p&gt;

&lt;p&gt;Why would you write a macro when you could write a function? Typically, it’s because you want to do write something like your own special form –  something that breaks Lisp’s normal execution plan. Macros are also typically evaluated at compile/interpret time not at runtime, so they might be faster than functions.&lt;/p&gt;

&lt;p&gt;I’m trying to find a decent example – not really being skilled enough to generate one on my own, and here’s one that might resonate with Ruby devs. From &lt;a href=&quot;https://piembsystech.com/using-macros-for-code-generation-in-lisp-programming-language/&quot;&gt;here&lt;/a&gt; – I tried to adapt to Clojure but failed.&lt;/p&gt;

&lt;p&gt;Let’s say you were writing a lot of similar log functions – you could write them all as separate functions&lt;/p&gt;

&lt;div class=&quot;language-lisp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defun&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;log-debug&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;DEBUG: ~a~%&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; 

&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defun&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;log-info&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;INFO: ~a~%&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; 

&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defun&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;log-warning&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;WARNING: ~a~%&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; 

&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defun&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;log-error&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;ERROR: ~a~%&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s fine but you could also do this more metaprogrammatically with a macro:&lt;/p&gt;

&lt;div class=&quot;language-lisp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defmacro&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;generate-log-function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;level&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
  &lt;span class=&quot;o&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defun&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;intern&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;concatenate&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;&apos;string&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;log-&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;symbol-name&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;level&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;~a: ~a~%&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;string-upcase&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;symbol-name&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;level&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt; 

&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;generate-log-function&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;debug&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;generate-log-function&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;generate-log-function&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;warning&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;generate-log-function&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Without going into the syntax, the basic idea here is that &lt;code class=&quot;highlighter-rouge&quot;&gt;defmacro&lt;/code&gt; takes in a level argument and generates a list that starts with &lt;code class=&quot;highlighter-rouge&quot;&gt;defun&lt;/code&gt; and defines the same function listed above.&lt;/p&gt;

&lt;p&gt;And so, a lot of writing complex programs in Lisp starts out by using macros and functions to turn Lisp into the language that you actually wanted to write the code in. And I guess that’s sort of true of most languages, but it’s more true of Lisp. The fact that Lisp programs are manageable as data makes incredibly easy to create your own language constructs. It also means that sufficiently complex Lisp programs are basically nothing like each other, aside from all the parentheses.&lt;/p&gt;

&lt;p&gt;If Perl is like being handed a Swiss army knife with no handle, and Smalltalk is like being handed a whole computer, Lisp is kind of like being handed a bunch of very well designed, but small, Lego bricks. The good news is you can piece them together into anything, and the bad news is that you have to piece them together into everything.&lt;/p&gt;

&lt;p&gt;As to what happened to Lisp, well, I’m not sure anything in particular happened. It was basically an academic language that also was big in the early open-source community. It turned out that having data and code be the same thing was not nearly as helpful an AI technique as people hoped.&lt;/p&gt;

&lt;p&gt;There’s an often cited article called &lt;a href=&quot;https://www.winestockwebdesign.com/Essays/Lisp_Curse.html&quot;&gt;The Lisp Curse, by Rudolf Winestock&lt;/a&gt; – written in 2011, the article posits that Lisp’s expressive power made it less likely to become popular, because:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Lisp is so powerful that problems which are technical issues in other programming languages are social issues in Lisp.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The idea is that it’s so easy to do complex things in Lisp that individual coders do them on their own, and more to the point that Lisp attracts programmers that want to do these things on there own, and so there’s no push to create community solutions that can be built on. Feels like the “my biggest weakness is I work too hard” of programming systems, but there’s probably something to it.&lt;/p&gt;

&lt;h2 id=&quot;ruby-and-lisp&quot;&gt;Ruby and Lisp&lt;/h2&gt;

&lt;p&gt;I think that what Ruby took from Lisp was more vibes than syntax. Concepts that seem important in Ruby and are also important in Lisp include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Everything is an expression, which is trivially true in Lisp, and an important part of Ruby&lt;/li&gt;
  &lt;li&gt;Domain-Specific Languages, or the idea that you’d use metaprogramming to create a mini language that more clearly expresses intent. Lisp and Ruby do that in different ways, but there’s an underlying similarity of spirit.&lt;/li&gt;
  &lt;li&gt;Lisp is also one of the few languages that differentiate strings than symbols.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I really do hope that you’ve enjoyed this series, it’s been a ton of fun to write, and it’s made we want to go back and play with these languages, and learn more languages. Basically, I love programming languages…&lt;/p&gt;</content><author><name>Noel Rappin</name></author><category term="programming" /><category term="history" /><category term="ruby" /><category term="lisp" /></entry><entry><title type="html">Ruby And Its Neighbors: Smalltalk</title><link href="https://noelrappin.com/2025/11/ruby-and-its-neighbors-smalltalk/" rel="alternate" type="text/html" title="Ruby And Its Neighbors: Smalltalk" /><published>2025-11-04T00:00:00+00:00</published><updated>2025-11-04T00:00:00+00:00</updated><id>repo://blog.collection/_blog/2025-11-04-ruby-and-its-neighbors-smalltalk.md</id><content type="html" xml:base="https://noelrappin.com/2025/11/ruby-and-its-neighbors-smalltalk/">&lt;p&gt;&lt;a href=&quot;https://noelrappin.com/blog/2025/10/ruby-and-its-neighbors-perl/&quot;&gt;Last time, we talked about Perl as an influence on Ruby&lt;/a&gt;, this time, we’ll talk about the other major influence on Ruby: Smalltalk.&lt;/p&gt;

&lt;p&gt;Smalltalk had a different kind of influence, since almost nothing of Smalltalk’s syntax made into Ruby. But many of the details of how objects work are directly inspired by Smalltalk, including the idea that every piece of data is part of the object system.&lt;/p&gt;

&lt;p&gt;Also unlike Perl, I spent a good couple of years working in Smalltalk, and it is one of my favorite languages that I’ll never likely use in anger again.&lt;/p&gt;

&lt;h2 id=&quot;a-personal-history-of-smalltalk&quot;&gt;(A Personal) History of Smalltalk&lt;/h2&gt;

&lt;p&gt;Smalltalk originated in the same Xerox PARC team that invented the windowed interface, ethernet, and the laser printer, and who knows what else, they may have invented ice cream and rainbows.&lt;/p&gt;

&lt;p&gt;There’s a whole story about what project Smalltalk was invented to be a part of, and a whole alternate history of computing and how people interact with computers that we are going to largely ignore. (If you are interested, start by searching for “Alan Kay Dynabook”.)&lt;/p&gt;

&lt;p&gt;Smalltalk went through a few different iterations in the 1970s, but the version that we know today is a direct descendent of Smalltalk-80, which was the first version released to the wider world.&lt;/p&gt;

&lt;p&gt;For most of the 80s and 90s, Smalltalk was something that doesn’t really exist today – a programming language and environment that companies paid money to use. Lots of money. The major player was ParcPlace, which was a spinoff of Xerox that provided Smalltalk tools. Their commercial product was originally called ObjectWorks, later changed to VisualWorks, and eventually sold off and presumably slowly losing customers after the late 90s.&lt;/p&gt;

&lt;p&gt;Smalltalk was pretty big in the industry for a while. Most of the aviation industry ran on it in the 90s, the big payroll project that was the basis for Extreme Programming was a Smalltalk project, there was reasonably high demand for Smalltalk programmers through at least the mid 1990s. I taught an undergrad OO class in Smalltalk in 1997 and 1998 to students that wanted to be learning C++, and I remember telling them that Smalltalk programmers were paid more.&lt;/p&gt;

&lt;p&gt;I first encountered Smalltalk as a grad student in about 1993, where Georgia Tech used ObjectWorks to teach Smalltalk and Object-Oriented programming (there’s a whole other sidebar about how Object-Oriented languages came to prominence in the 90s, and the arguments over that but again, another day). ObjectWorks was pricey, and there was also a lower-cost vendor called Digitalk, and eventually I also used a product called Smalltalk Agents, which has apparently totally vanished from the entire internet.&lt;/p&gt;

&lt;p&gt;In 1995, a bunch of the original Xerox Smalltalk team was together at Apple, and they decided to release an open-source Smalltalk VM. What they did was very interesting. They wrote a very, very small kernel in very vanilla C, and then 95% of the environment was then built in Smalltalk on top of that. Oh, and even the vanilla C was written in Smalltalk, they wrote a Smalltalk to C compiler. They called their new Smalltalk “Squeak”, which made a lot more sense when they all moved en masse to Disney.&lt;/p&gt;

&lt;p&gt;The fact that Squeak was largely written in itself made it fairly easy to port to new systems, and it was quickly available on just about anything with a microchip.&lt;/p&gt;

&lt;p&gt;I’m pretty sure that I first saw Squeak at the OOPSLA conference in 1997. (Object-Oriented Programming, Systems, Languages &amp;amp; Applications, since you asked) At this conference I somehow got to do a team-building exercise with Adelde Goldberg from the original Xerox PARC team, which is not relevant to anything but seemed very cool at the time. I was already using Smalltalk in my projects, but Squeak was immediately interesting and my extended research team started doing cool stuff. Like, what I’m pretty sure was the first Wiki tool outside the original C2 Wiki, was written in Squeak. (Apparently at least &lt;a href=&quot;https://wiki.squeak.org/squeak&quot;&gt;one&lt;/a&gt; is still running).&lt;/p&gt;

&lt;h2 id=&quot;smalltalks-environment&quot;&gt;Smalltalk’s Environment&lt;/h2&gt;

&lt;p&gt;It’s important to understand that Smalltalk’s development is a different evolutionary tree from nearly every currently popular programming language, in that Smalltalk is in no way, shape, or form influenced by Unix or C. Perl, Ruby, Python, JavaScript, Swift, Kotlin and on and on, all come from a universe where they expect to run Unix libraries, and where C syntax is normal. The Unix philosophy of “small pieces, loosely joined” is not a part of Smalltalk’s DNA at all.&lt;/p&gt;

&lt;p&gt;Smalltalk is basically its own operating system, and the syntax is different from C-style languages in ways big and small. For example, the first element of an array is… 1. Which, when you think about how people count, actually makes sense.&lt;/p&gt;

&lt;p&gt;It’s hard to separate Smalltalk the language from Smalltalk the environment, although I suppose technically you could have the language without the whole shebang (and I think there was a GNU Smalltalk that tried this), really the environment is part of the appeal.&lt;/p&gt;

&lt;p&gt;Your main interfaces to the smalltalk system are a &lt;em&gt;Workspace&lt;/em&gt; and a &lt;em&gt;Browser&lt;/em&gt;. A workspace is analogous to REPL session, you can type in arbitrary Smalltalk code and have the system “do it” to execute the code, “print it” to execute the code and output the result. There are some other actions like “debug it” or “inspect it”, but that’s the basic idea. Unlike a Unix REPL, there’s no prompt, and you don’t automatically invoke code by hitting return, you have to select code and then invoke the menu item or the keyboard shortcut for the code you want to act on.&lt;/p&gt;

&lt;p&gt;The Browser is where you write code. There a a few different versions in most Smalltalks, here’s the main one, this is from a modern Smalltalk called &lt;a href=&quot;https://cuis.st/&quot;&gt;Cuis&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;browser.png&quot; alt=&quot;A Smalltalk browser&quot; /&gt;&lt;/p&gt;

&lt;p&gt;At the top, we have four window panes – left to right we have:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Categories – groups of classes that are related in some way. Cuis nicely puts each group in a pulldown list. Categories have no particular syntactic meaning, they are just there to make browsing easier.&lt;/li&gt;
  &lt;li&gt;Classes – one entry for each class in the currently selected category, at the bottom of this pane is a toggle for “class” vs. “instance” which determines what kinds of messages are shown in the next two panes.&lt;/li&gt;
  &lt;li&gt;Protocols – a protocol is a user-defined group of messages. Smalltalk internally uses “message” rather than “method” because of how Alan Kay thinks about objects. Again, protocols are for the programmer, not the system.&lt;/li&gt;
  &lt;li&gt;Messages – each messages in the currently selected protocol is listed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bottom pane is the code editor, and if a message is selected in the code pane, its code is displayed there.&lt;/p&gt;

&lt;p&gt;You probably have questions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this mean that you can see the source code for the entire Smalltalk system?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes, yes it does.&lt;/p&gt;

&lt;p&gt;**Can you modify any code in the system? **&lt;/p&gt;

&lt;p&gt;Yes, yes you can.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Even, like, deep system code?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Isn’t that dangerous?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As a Ruby developer, you should know that it’s only as dangerous as the developers who use it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do you edit a message?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Just display the existing message in the browser, edit the message and select “save”. The Smalltalk system will parse the code, stop if there are syntax errors, but if not, the updated method will be saved to the system. A side effect is you can’t save code that isn’t syntactically parsable, even as a draft.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Okay, but how do you &lt;em&gt;create&lt;/em&gt; a message?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The “real” way is to select a protocol but not a message, and Smalltalk will put a template in the edit window. Write your message in the editor and save it. Alternately, you can just change the name of a message in the edit window, and a new method with that name will be created, without deleting the old message.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And how do you create a class?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Similarly.&lt;/p&gt;

&lt;p&gt;If you select a category and not a class, you’ll get this in the code editor pane:&lt;/p&gt;

&lt;div class=&quot;language-smalltalk highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;subclass:&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;#NameOfSubclass&lt;/span&gt;
	&lt;span class=&quot;nf&quot;&gt;instanceVariableNames:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;&apos;&lt;/span&gt;
	&lt;span class=&quot;nf&quot;&gt;classVariableNames:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;&apos;&lt;/span&gt;
	&lt;span class=&quot;nf&quot;&gt;poolDictionaries:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;&apos;&lt;/span&gt;
	&lt;span class=&quot;nf&quot;&gt;category:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;Kernel-Chronology&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The thing to note is that this is not actually template, it’s actually code: a message, waiting for you to fill in the arguments, replacing &lt;code class=&quot;highlighter-rouge&quot;&gt;#NameOfSubclass&lt;/code&gt; and adding the instance variables and so on. You don’t save this, you “do it”, just like if you were in a workspace. The message call is evaluated, and Smalltalk creates a new class.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But wait, if all the code is in the image and isn’t in text files, how do people work together and share code?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Don’t worry about it.&lt;/p&gt;

&lt;p&gt;Seriously, though, worry about it.&lt;/p&gt;

&lt;p&gt;This has always been a problem. Smalltalk allows you to share “change sets”, effectively the code differences between one point and another. Classically, one person would export their change set, and other team members would import it. Different Smalltalks have built up more sophisticated tools over time.&lt;/p&gt;

&lt;h2 id=&quot;smalltalks-syntax&quot;&gt;Smalltalk’s Syntax&lt;/h2&gt;

&lt;p&gt;Smalltalk’s syntax is very simple, relative to Ruby and Perl.&lt;/p&gt;

&lt;p&gt;Wait a sec, I literally wrote this for a chapter in a book about Smalltalk literally 25 years ago, here’s a slight paraphrase:&lt;/p&gt;

&lt;p&gt;Every line of Smalltalk is evaluated the same way.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Every variable is an object. There are no basic types that are not objects.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Every expression is a message being passed to an object, there is basically no expression syntax that is not a message.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;All messages return a value. (The return value is specified by &lt;code class=&quot;highlighter-rouge&quot;&gt;^&lt;/code&gt;, if the method does not specify a return value, it implicitly returns &lt;code class=&quot;highlighter-rouge&quot;&gt;self&lt;/code&gt;, the instance that received the message.)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;There are three kinds of messages:
    &lt;ul&gt;
      &lt;li&gt;Unary messages like &lt;code class=&quot;highlighter-rouge&quot;&gt;3 negated&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;Binary messages like &lt;code class=&quot;highlighter-rouge&quot;&gt;a + b&lt;/code&gt;, these actually are messages you can define, there is a small set of them, and they are special cases in the parser.&lt;/li&gt;
      &lt;li&gt;Keyword messages such as &lt;code class=&quot;highlighter-rouge&quot;&gt;anArray at: 3 put: 7&lt;/code&gt;. This syntax got used by ObjectiveC and later Swift, so you may be familiar with it. It’s &lt;code class=&quot;highlighter-rouge&quot;&gt;receiver &amp;lt;messagepart&amp;gt;: &amp;lt;argument&amp;gt;&lt;/code&gt; where you can have multiple message parts. If you are referring to the message, typically you just say the message parts, so this message would be called &lt;code class=&quot;highlighter-rouge&quot;&gt;at:put:&lt;/code&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Smalltalk does not have operator precedence. All code is evaluated strictly from left to right. Unary messages first, binary messages second, keyword messages last. Parenthesis can be used to force order of operations or to make things clearer.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;The assignment operator is &lt;code class=&quot;highlighter-rouge&quot;&gt;:=&lt;/code&gt; (Smalltalk uses &lt;code class=&quot;highlighter-rouge&quot;&gt;=&lt;/code&gt; for boolean equality), the right hand side is evaluated and the value is assigned to the result of the left hand side.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And that’s basically it, with a couple of ways to create literals like strings, arrays, dictionaries, local variables, and blocks.&lt;/p&gt;

&lt;p&gt;So, for 10 points and control of the board, what does this do?&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;hypotenuse := 3 squared + 4 squared sqrt&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The unary messages are evaluated first:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;hypotenuse := 9 + 16 sqrt&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;There’s still a unary message&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;hypotenuse := 9 + 4&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now we can do the binary message:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;hypotenuse = 13&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Oops.&lt;/p&gt;

&lt;p&gt;To get what you actually want, you need parentheses:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;hypotenuse := (3 squared + 4 squared) sqrt&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Smalltalk does not have special syntax for loops, all loop behavior is defined by methods on &lt;code class=&quot;highlighter-rouge&quot;&gt;Array&lt;/code&gt; and the like, very similar to Ruby’s &lt;code class=&quot;highlighter-rouge&quot;&gt;Enumerable&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Smalltalk does not have special syntax to create messages or classes. Message creation is managed by the editor (which internally calls a message that adds the new code), class creation is just another method – in Squeak, that method is &lt;code class=&quot;highlighter-rouge&quot;&gt;Object#subclass:instanceVariableNames:classVariableNames:poolDictionaries:category:&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Smalltalk does not have special syntax for boolean logic, all logic behavior is defined by the classes &lt;code class=&quot;highlighter-rouge&quot;&gt;True&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;False&lt;/code&gt;. Ruby sort of does this, but Ruby does have &lt;code class=&quot;highlighter-rouge&quot;&gt;if&lt;/code&gt; as special syntax. Smalltalk does not, you’d write a Smalltalk conditional as just another message:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;(x &amp;gt; 10) ifTrue: [ x squared ] ifFalse: [ x sqrt ]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The square brackets are blocks, and behave very similar to Ruby blocks, except that you can treat them as just normal variables and normal arguments. You can even, as in this case, have multiple arguments that take blocks.&lt;/p&gt;

&lt;p&gt;The implementation if the method &lt;code class=&quot;highlighter-rouge&quot;&gt;ifTrue:ifFalse&lt;/code&gt; is simple. For the &lt;code class=&quot;highlighter-rouge&quot;&gt;True&lt;/code&gt; class, it just takes the true block and executes it by passing it the message &lt;code class=&quot;highlighter-rouge&quot;&gt;value&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ifTrue: trueAlternativeBlock ifFalse: falseAlternativeBlock
	^trueAlternativeBlock value
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And for the false class, the exact opposite:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ifTrue: trueAlternativeBlock ifFalse: falseAlternativeBlock
	^falseAlternativeBlock value
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Smalltalk doesn’t have a &lt;code class=&quot;highlighter-rouge&quot;&gt;case&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;switch&lt;/code&gt; statement, typically if you want behavior like that you’d define a dictionary of keys to blocks or you would use the object system and polymorphism and double dispatch.&lt;/p&gt;

&lt;h2 id=&quot;smalltalks-object-model&quot;&gt;Smalltalk’s Object Model&lt;/h2&gt;

&lt;p&gt;There’s a lot about Smalltalk’s object model that sound familiar to a Ruby developer:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;There’s a base class called &lt;code class=&quot;highlighter-rouge&quot;&gt;Object&lt;/code&gt; that everything inherits from.&lt;/li&gt;
  &lt;li&gt;Instance variables are private. Getters and setters default to having the same name as the instance variable.&lt;/li&gt;
  &lt;li&gt;Method lookup happens at the point of the method call.&lt;/li&gt;
  &lt;li&gt;Classes are instances of the class &lt;code class=&quot;highlighter-rouge&quot;&gt;Class&lt;/code&gt; (sort of).&lt;/li&gt;
  &lt;li&gt;There’s a thing called a “Metaclass”&lt;/li&gt;
  &lt;li&gt;There’s a method that’s the method of last resort – in Ruby, it’s &lt;code class=&quot;highlighter-rouge&quot;&gt;method_missing&lt;/code&gt;, but in Smalltalk it’s called &lt;code class=&quot;highlighter-rouge&quot;&gt;doesNotUnderstand&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are a couple of differences as well&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Smalltalk’s meta classes are structured differently, I &lt;a href=&quot;https://noelrappin.com/blog/2025/01/better-know-a-ruby-thing-singleton-classes/&quot;&gt;explained this once&lt;/a&gt; and I’m not sure I ever want to explain it again.&lt;/li&gt;
  &lt;li&gt;Smalltalk doesn’t have multiple inheritance or mixins or modules or anything like that. Although there have been some attempts to add these features, the traditional Smalltalk way to do this is through delegation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But overall, Smalltalk and Ruby are similar enough that a huge amount of Kent Beck’s &lt;em&gt;Smalltalk Best Practice Patterns&lt;/em&gt; is applicable to Ruby as long as you translate the syntax.&lt;/p&gt;

&lt;h2 id=&quot;what-happened&quot;&gt;What Happened?&lt;/h2&gt;

&lt;p&gt;Unlike Perl, I actually did use Smallalk to build a few real applications that had real users. I miss it a lot.&lt;/p&gt;

&lt;p&gt;I find that when I try to explain Smalltalk to people, it’s easy to explain the syntax and the object model. What’s hard to explain is how it is to work in a Smalltalk environment.&lt;/p&gt;

&lt;p&gt;You’ve likely used powerful coding editors and terminals. Smalltalk is just different. You are in the running environment.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Tests start instantly, and in general run very fast. There’s a dedicated test runner window. Some smalltalk integrate tests with the regular browser, so you can see test status from the code browsers.&lt;/li&gt;
  &lt;li&gt;Debugging is amazing, you can investigate the state of any object in the system, you can change that state, you can easily execute arbitrary code. You can have a test halt on exception, update the code and re-run from the point failure. It’s hard to describe how fluid it is, especially since I’m no longer expert enough to do it fluently.&lt;/li&gt;
  &lt;li&gt;While the editor doesn’t have all the niceties of the IDE’s you are used to, it’s very powerful in its own way. If you save code with a message name that does not appear in the image at all, Smalltalk will typically ask you if you want to define it right there. A lot of the things we ask a Language Server to do, Smalltalk just kind of does, because the image has access to everything.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the all-encompassing nature of the environment was also Smalltalk’s downfall. As more and more of the general computing environment became Unix and the “small pieces loosely joined” philosophy, Smalltalk got harder and harder to integrate. Smalltalk isn’t a scripting language, it was late to develop connectivity to external databases, its model of team interaction is fundamentally different from Unix source control. The image-based system has some drawbacks – you do get amazing access to the system, but it can be hard to tell where your code ends and the system begins. Code could depend on the state of the image in ways that were hard to replicate in deploys.&lt;/p&gt;

&lt;h2 id=&quot;what-did-ruby-take-from-smalltalk&quot;&gt;What Did Ruby Take From Smalltalk?&lt;/h2&gt;

&lt;p&gt;Smalltalk’s legacy in Ruby is primarily the object model – the idea that everything is an object and everything is manageable via method calls, and that message calls are evaluated at the point of call, as late as possible. Ruby takes that idea and translates it into a syntax that is more familiar to programmers used to C/Perl/Java.&lt;/p&gt;

&lt;p&gt;I’m not sure this is exactly on point as far as Smalltalk’s influence on Ruby, but my Ruby style has always been very aggressive about creating new classes and objects. I’m quite confident that a reason for that style is that I came from Smalltalk first and not Java, Smalltalk style is much more amenable to small classes.&lt;/p&gt;

&lt;p&gt;On my first largish Smalltalk project, users were simulating a chemical plant’s pipe system by placing tiles with pipes in them, and I frequently needed to do logic based on relative directions. I clearly remember creating a &lt;code class=&quot;highlighter-rouge&quot;&gt;Direction&lt;/code&gt; class with basically four live instances, &lt;code class=&quot;highlighter-rouge&quot;&gt;up&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;down&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;left&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;right&lt;/code&gt;, and just enough logic inside to say that &lt;code class=&quot;highlighter-rouge&quot;&gt;up.turn_left&lt;/code&gt; equals &lt;code class=&quot;highlighter-rouge&quot;&gt;left&lt;/code&gt;, but &lt;code class=&quot;highlighter-rouge&quot;&gt;down.turn_left&lt;/code&gt; equals &lt;code class=&quot;highlighter-rouge&quot;&gt;right&lt;/code&gt;. It was useful enough that I remember how much fun it was to build it even  now, nearly thirty years later.&lt;/p&gt;

&lt;p&gt;Of all the other programming languages I’ve used, Ruby is the one that most clearly encourages that style of coding.&lt;/p&gt;</content><author><name>Noel Rappin</name></author><category term="programming" /><category term="history" /><category term="ruby" /><category term="smalltalk" /></entry><entry><title type="html">Ruby And Its Neighbors: Perl</title><link href="https://noelrappin.com/2025/10/ruby-and-its-neighbors-perl/" rel="alternate" type="text/html" title="Ruby And Its Neighbors: Perl" /><published>2025-10-13T00:00:00+00:00</published><updated>2025-10-13T00:00:00+00:00</updated><id>repo://blog.collection/_blog/2025-10-13-ruby-and-its-neighbors-perl.md</id><content type="html" xml:base="https://noelrappin.com/2025/10/ruby-and-its-neighbors-perl/">&lt;p&gt;Ruby takes a large part of its inspiration from two older languages:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Perl for general syntax and design philosophy&lt;/li&gt;
  &lt;li&gt;Smalltalk for Object-Oriented structure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ve been in kind of a writers block, for all kinds of reasons, personal and professional. I started to think about an article that I could write that would get my fingers typing, and drifted into what I could do for another “Better Know A Ruby Thing”, started thinking about String literals, wondered how I would answer the question of why Ruby has so many ways to write String literals.&lt;/p&gt;

&lt;p&gt;The answer to that is that most of them were inherited from Perl.&lt;/p&gt;

&lt;p&gt;In fact, a lot of Ruby’s more unusual syntax was taken from Perl.&lt;/p&gt;

&lt;p&gt;And then I realized that Perl has vanished so completely that there’s probably a large group of Ruby developers that don’t know much about it. I’m looking at the documentation for the newish &lt;a href=&quot;http://zed.dev&quot;&gt;Zed editor&lt;/a&gt; and I note that it does not mention Perl in languages supported…&lt;/p&gt;

&lt;p&gt;Oh wait — a quick story. About 10 years ago, when &lt;em&gt;Take My Money&lt;/em&gt; came out, I was doing user groups in the Chicago area on the forlorn hope that somebody might buy a book. For some reason I did the Perl Monger’s user group, which still existed even in 2017. It was really interesting — very rare even than that I was below the median age. But almost all of them had banking experience, so they didn’t need me to tell them how to do money stuff. But they were &lt;em&gt;fascinated&lt;/em&gt; by Ruby.&lt;/p&gt;

&lt;p&gt;So, it’s been almost 20 years since I had to know any of this, so I’m pretty sure that I’ll make some factual errors here in this description, so, apologies in advance…&lt;/p&gt;

&lt;h3 id=&quot;what-is-perl&quot;&gt;What Is Perl?&lt;/h3&gt;

&lt;p&gt;Perl was once &lt;em&gt;the&lt;/em&gt; scripting language in a way that no individual language can manage the days. Its first public release was 1987, and its bible, &lt;em&gt;Programming Perl&lt;/em&gt; by Larry Wall, was first printed in 1991, and is the basis for the big phone-book overview of a programming language. It’s always been known as “The Camel Book”. (This isn’t exactly something you can easily look up, but I think the first programming book to be known by it’s cover image is “The Dragon Book”, which is the big book on compilers).&lt;/p&gt;

&lt;p&gt;The Camel Book is probably the best-selling book about a single programming language, or at least it was, it may have been overtaken by &lt;em&gt;JavaScript: The Good Parts&lt;/em&gt; or something.&lt;/p&gt;

&lt;p&gt;Perl was a couple of things. It was the first really popular open source language and language community. It was also the language of the very early web, as the first dynamic web pages were overwhelmingly written in Perl.&lt;/p&gt;

&lt;p&gt;There are two things that help Perl make sense:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;It was designed to make string manipulation super-easy, especially with regular expressions. And in this case “super-easy” means: with a minimum of typing.&lt;/li&gt;
  &lt;li&gt;Larry Wall had a linguistics background, and wanted Perl to have the flexibility of natural language, and as a result, there are many different ways to do most things.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice these two features made for a language that was extremely flexible and also could be very &lt;a href=&quot;https://en.wikipedia.org/wiki/Obfuscated_Perl_Contest&quot;&gt;hard to read&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Another huge advantage of Perl was CPAN, a package manager that enabled third-party packages to easily be found and installed locally. It’s genuinely weird to explain how hard it was to install most open source stuff before about 2005. But CPAN was so far ahead of its time in 1999 that I’m pretty sure it had features that RubyGems still doesn’t have. It felt like a magic trick.&lt;/p&gt;

&lt;h3 id=&quot;perl-syntax-for-rubyists&quot;&gt;Perl Syntax for Rubyists&lt;/h3&gt;

&lt;p&gt;I got weirdly nostalgic looking back over Perl code, even though I never really wrote much Perl. In fact, I left a job in part because I wanted to do a project in Python and they wanted it in Perl. (Okay, that was a symptom of deeper problems, it’s funnier to say I quit a job because they asked me to write Perl.)&lt;/p&gt;

&lt;p&gt;Even though I didn’t write much Perl, it was ubiquitous in the late 90s, you could go an any programmer forum and use &lt;code class=&quot;highlighter-rouge&quot;&gt;s/this/that&lt;/code&gt; to describe a text change and people would know what you meant.&lt;/p&gt;

&lt;p&gt;Here are some of the Perl features that struck me as I was looking back over some Perl references.&lt;/p&gt;

&lt;h4 id=&quot;variable-sigils&quot;&gt;Variable Sigils&lt;/h4&gt;

&lt;p&gt;Ruby gets the use of &lt;code class=&quot;highlighter-rouge&quot;&gt;@&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;$&lt;/code&gt; as variable prefixes from Perl, but Perl uses prefixes differently. In Perl, &lt;em&gt;all&lt;/em&gt; scalar variable names start with &lt;code class=&quot;highlighter-rouge&quot;&gt;$&lt;/code&gt; and all list variable names start with &lt;code class=&quot;highlighter-rouge&quot;&gt;@&lt;/code&gt; – &lt;code class=&quot;highlighter-rouge&quot;&gt;$user&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;@user&lt;/code&gt; are two different variables. There’s also a prefix (the term in Perl is &lt;em&gt;sigil&lt;/em&gt;) for hashes (&lt;code class=&quot;highlighter-rouge&quot;&gt;%&lt;/code&gt;) and subroutines (&lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;amp;&lt;/code&gt;). At the time, I remember finding these vaguely annoying, but now I can see how important they are, because the sigils mean the parser doesn’t have to guess about what a variable name is, and Perl uses them for it’s somewhat idiosyncratic type management.&lt;/p&gt;

&lt;h4 id=&quot;automatic-conversion&quot;&gt;Automatic Conversion&lt;/h4&gt;

&lt;p&gt;Perl is generally quite permissive about types and tries to do &lt;em&gt;something&lt;/em&gt; with most expressions. In particular, Perl will automatically convert numbers and strings, so &lt;code class=&quot;highlighter-rouge&quot;&gt;&quot;3&quot; + 3&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;3 + &quot;3&quot;&lt;/code&gt; are both legal. What makes this possible is that Perl uses &lt;code class=&quot;highlighter-rouge&quot;&gt;+&lt;/code&gt; for addition and &lt;code class=&quot;highlighter-rouge&quot;&gt;.&lt;/code&gt; for string concatenation, so there’s no confusing &lt;code class=&quot;highlighter-rouge&quot;&gt;&quot;3&quot; + 3&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;&quot;3&quot; . 3&lt;/code&gt;, in both cases Perl converts the value that isn’t the correct type. (Perl also distinguishes between &lt;code class=&quot;highlighter-rouge&quot;&gt;*&lt;/code&gt; for multiplication and &lt;code class=&quot;highlighter-rouge&quot;&gt;x&lt;/code&gt; for String repetition).&lt;/p&gt;

&lt;p&gt;Perl will also automatically convert between lists and scalars based on context. And Perl will determine the context from, say, the lefthand side of an assignment statement. So &lt;code class=&quot;highlighter-rouge&quot;&gt;$count = @users + @companies&lt;/code&gt; is equivalent to Ruby’s &lt;code class=&quot;highlighter-rouge&quot;&gt;count = users.length + companies.length&lt;/code&gt;, because the &lt;code class=&quot;highlighter-rouge&quot;&gt;$count&lt;/code&gt; puts us in scalar context, and in scalar context for addition, Perl converts an array to the length of the array.&lt;/p&gt;

&lt;p&gt;All this type conversion is both kind of handy on quick scripts – we just saved 11 whole characters on that &lt;code class=&quot;highlighter-rouge&quot;&gt;count&lt;/code&gt; example. It’s also a great way to have subtle bugs in large codebases.&lt;/p&gt;

&lt;h4 id=&quot;hashes&quot;&gt;Hashes&lt;/h4&gt;

&lt;p&gt;Hashes in Perl have a &lt;code class=&quot;highlighter-rouge&quot;&gt;%&lt;/code&gt; sigil, but looking up a hash is a scalar, so &lt;code class=&quot;highlighter-rouge&quot;&gt;%user&lt;/code&gt; for the whole hash, but &lt;code class=&quot;highlighter-rouge&quot;&gt;$user{&apos;name&apos;}&lt;/code&gt; for a lookup value. Hashes can be made from lists and there’s no distinction between keys and values, they just alternate: &lt;code class=&quot;highlighter-rouge&quot;&gt;%user = (&apos;name&apos;, &apos;Noel&apos;, &apos;username&apos;, &apos;noelrap&apos;)&lt;/code&gt; – Ruby had this syntax originally but eventually deprecated it.&lt;/p&gt;

&lt;p&gt;But – Perl considers &lt;code class=&quot;highlighter-rouge&quot;&gt;=&amp;gt;&lt;/code&gt; to be completely equivalent to a comma, so you can write this as &lt;code class=&quot;highlighter-rouge&quot;&gt;%user = (&apos;name&apos; =&amp;gt; &apos;Noel&apos;, &apos;username&apos; =&amp;gt; &apos;noelrap&apos;)&lt;/code&gt;. Also, for maximum confusion, you could write it as &lt;code class=&quot;highlighter-rouge&quot;&gt;%user = (&apos;name&apos; =&amp;gt; &apos;Noel&apos; =&amp;gt; &apos;username&apos; =&amp;gt; &apos;noelrap&apos;)&lt;/code&gt;. Also you can omit quotes to the left of he arrow, so &lt;code class=&quot;highlighter-rouge&quot;&gt;%user = (name =&amp;gt; &apos;Noel&apos;, username =&amp;gt; &apos;noelrap&apos;)&lt;/code&gt;. This is a good example of how Perl is both great and exhausting.&lt;/p&gt;

&lt;h4 id=&quot;conditionals&quot;&gt;Conditionals&lt;/h4&gt;

&lt;p&gt;Perl uses &lt;code class=&quot;highlighter-rouge&quot;&gt;0&lt;/code&gt;, an empty string, and the string &lt;code class=&quot;highlighter-rouge&quot;&gt;&quot;0&quot;&lt;/code&gt; for logical false, it doesn’t otherwise have a boolean type. Perl is also (presumably), the source for Ruby’s use of &lt;code class=&quot;highlighter-rouge&quot;&gt;elsif&lt;/code&gt; in compound if statements. Why Wall used this in Perl I’m not sure—Bash uses &lt;code class=&quot;highlighter-rouge&quot;&gt;elif&lt;/code&gt; (and so does Python). I can see why having a dedicated else if keyword makes parsing easier, but it’s funny to me that in a language often dedicated to minimizing typing, Wall added the extra character there.&lt;/p&gt;

&lt;h4 id=&quot;strings&quot;&gt;Strings&lt;/h4&gt;

&lt;p&gt;Perl has a lot of features that Ruby pulled in – single and double quoted strings with interpolation in the string. In Perl, because of the variable sigils you can just include the variable name &lt;code class=&quot;highlighter-rouge&quot;&gt;&quot;Hello $name&quot;&lt;/code&gt; rather than having to enclose it in other syntax. Ruby actually also lets you do that, sort of, with variables with sigils, &lt;code class=&quot;highlighter-rouge&quot;&gt;&quot;Hello #@name&quot;&lt;/code&gt; is valid, if rarely used, Ruby.&lt;/p&gt;

&lt;p&gt;Perl has a shortcut for a list of strings &lt;code class=&quot;highlighter-rouge&quot;&gt;qw(one two three)&lt;/code&gt; and also has the same arbitrary delimiter behavior.&lt;/p&gt;

&lt;h4 id=&quot;global-default&quot;&gt;Global Default&lt;/h4&gt;

&lt;p&gt;One of Perl’s most common shortcuts in practice is the global default value, &lt;code class=&quot;highlighter-rouge&quot;&gt;$_&lt;/code&gt;, which is set automatically by reading from a file or console, or by the index of a loop if the index is not otherwise specified. Where this is a shortcut is that many Perl functions will use &lt;code class=&quot;highlighter-rouge&quot;&gt;$_&lt;/code&gt; as the argument if no argument is specified, and will also automatically set it with the result of the function. This means in practice you can leave off arguments in many places and things will just work, meaning that you can have a series of methods that appear to take no arguments, but are actually all taking in &lt;code class=&quot;highlighter-rouge&quot;&gt;$_&lt;/code&gt; and setting it to a new value for the next function. Like a lot of Perl, this is both convenient and hard to read.&lt;/p&gt;

&lt;p&gt;Perl is also the source of the “methods return the last evaluated value” thing. But Perl also defaults to not giving method arguments names. Instead, by default, Perl takes any arguments you pass and throws them all in a magic array value &lt;code class=&quot;highlighter-rouge&quot;&gt;@_&lt;/code&gt;, which you can either use directly or deference into local variables. Local varables in Perl are defined with the function &lt;code class=&quot;highlighter-rouge&quot;&gt;my&lt;/code&gt; as in &lt;code class=&quot;highlighter-rouge&quot;&gt;my $name = &quot;Noel&quot;;&lt;/code&gt;&lt;/p&gt;

&lt;h4 id=&quot;regular-expressions&quot;&gt;Regular Expressions&lt;/h4&gt;

&lt;p&gt;Regular expressions are the big star of the syntax, Perl has the same &lt;code class=&quot;highlighter-rouge&quot;&gt;/pattern/&lt;/code&gt; literal syntax that Ruby has, but Perl considers the slash to be an operator, not a literal creating an object&lt;/p&gt;

&lt;p&gt;This means you can do all kinds of shortcuts.&lt;/p&gt;

&lt;p&gt;For example, a raw pattern by default matches against &lt;code class=&quot;highlighter-rouge&quot;&gt;$_&lt;/code&gt;, so&lt;/p&gt;

&lt;div class=&quot;language-perl highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;vg&quot;&gt;$_&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Noel Rappin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/N.*/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;this is a match&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here the &lt;code class=&quot;highlighter-rouge&quot;&gt;if&lt;/code&gt; is matching the pattern against the default variable and returning a truthy value if the pattern matches, and a falsey value (specifically &lt;code class=&quot;highlighter-rouge&quot;&gt;0&lt;/code&gt;) if it doesn’t. I’m explicitly setting &lt;code class=&quot;highlighter-rouge&quot;&gt;$_&lt;/code&gt; there, but you don’t have to, you could use one of the many ways that &lt;code class=&quot;highlighter-rouge&quot;&gt;$_&lt;/code&gt; is implicitly set.&lt;/p&gt;

&lt;p&gt;Perl also has &lt;code class=&quot;highlighter-rouge&quot;&gt;~=&lt;/code&gt; but only for string on the left, pattern on the right, and it’s where Ruby got &lt;code class=&quot;highlighter-rouge&quot;&gt;$1&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;$2&lt;/code&gt; and so on for match variables – much more commonly used in Perl than in modern Ruby. Also &lt;code class=&quot;highlighter-rouge&quot;&gt;$&amp;amp;&lt;/code&gt; for the entire match, $` for the part of the string before the match and &lt;code class=&quot;highlighter-rouge&quot;&gt;$&apos;&lt;/code&gt; for the part of the string after the match.&lt;/p&gt;

&lt;p&gt;Perl also has a special substitution operator: &lt;code class=&quot;highlighter-rouge&quot;&gt;s///&lt;/code&gt; – the first part of the string that matches the pattern between the first two slashes is replaced by whatever is between the last two slashes. The result, if not assigned, goes to &lt;code class=&quot;highlighter-rouge&quot;&gt;$_&lt;/code&gt;. To get multiple replacements, you use &lt;code class=&quot;highlighter-rouge&quot;&gt;g///&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The upshot of all this is a language that can be quite compact, especially if you are in a situation where you are using &lt;code class=&quot;highlighter-rouge&quot;&gt;$_&lt;/code&gt; a lot. But there is also a lot of room for personal styles, to the point where it can be very hard to read somebody else’s Perl.&lt;/p&gt;

&lt;h3 id=&quot;object-oriented-perl&quot;&gt;Object Oriented Perl&lt;/h3&gt;

&lt;p&gt;Objects in Perl are worth a few words. Basically, what Perl does is let you create packages that contain a bunch of methods, similar to a Ruby &lt;code class=&quot;highlighter-rouge&quot;&gt;Module&lt;/code&gt;. You can than use the built-in function &lt;code class=&quot;highlighter-rouge&quot;&gt;bless&lt;/code&gt; to an arbitrary data structure – usually a hash – into an “instance” of that class for the purposes of method lookup .&lt;/p&gt;

&lt;p&gt;So if you had an existing file called &lt;code class=&quot;highlighter-rouge&quot;&gt;company.pl&lt;/code&gt; that defined &lt;code class=&quot;highlighter-rouge&quot;&gt;package Company;&lt;/code&gt; that defined a method called &lt;code class=&quot;highlighter-rouge&quot;&gt;get_continent&lt;/code&gt;, you could then do this:&lt;/p&gt;

&lt;div class=&quot;language-perl highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;Company&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;$company&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bless&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Viridian Dynamics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&quot;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;country&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;USA&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&quot;&lt;/span&gt;
   &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;Company&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;$company&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;get_continent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;bless&lt;/code&gt; method associates that hash with the &lt;code class=&quot;highlighter-rouge&quot;&gt;Company&lt;/code&gt; package. After that, using &lt;code class=&quot;highlighter-rouge&quot;&gt;-&amp;gt;&lt;/code&gt; followed by the name of a subroutine in that package causes Perl to call that subroutine with the hash object as the first argument. In practice, most Perl packages designed to be used as classes provided a &lt;code class=&quot;highlighter-rouge&quot;&gt;new&lt;/code&gt; subroutine that did the &lt;code class=&quot;highlighter-rouge&quot;&gt;bless&lt;/code&gt; call inside it.&lt;/p&gt;

&lt;p&gt;The Ruby equivalent would be… I guess taking a hash and including a module inside its singleton class? There isn’t really a Ruby equivalent because Ruby has real classes.&lt;/p&gt;

&lt;p&gt;Also, nothing in Perl is private, so you can always just get at the original data in the hash. To quote Larry Wall:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Perl doesn’t have an infatuation with enforced privacy. It would prefer that you stayed out of its living room because you weren’t invited, not because it has a shotgun”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Having genuinely tried to use this part of Perl, albeit briefly and 20 years ago, it’s generally okay at “here’s some fancy methods specific to this data shape”. But, the idea of encapsulation is sort of fundamentally opposite to the Perl aesthetic, so it always felt a little weird.&lt;/p&gt;

&lt;p&gt;I do remember being pretty heavily influenced by the book &lt;em&gt;Perl Best Practices&lt;/em&gt;, which functioned as “Perl: The Good Parts”, and I think it advised some specific OO practices to make using it manageable.&lt;/p&gt;

&lt;h3 id=&quot;what-did-ruby-take&quot;&gt;What did Ruby Take?&lt;/h3&gt;

&lt;p&gt;It’s always been acknowledged that Ruby comes from Perl, the name “Ruby” is allegedly because pearl is the birthstone for November and ruby is the birthstone for December.&lt;/p&gt;

&lt;p&gt;Very broadly, what Ruby took from Perl is syntax and to some extent design inspiration, and what Ruby added to Perl is genuine object-orientation.&lt;/p&gt;

&lt;p&gt;A tremendous amount of Ruby’s syntax is adapted from Perl – just scratching the surface, there are string literals, regex literals, the if statement, here docs, the ability to have custom delimiters, underscore in number literals, the use of sigils at the start of variables, using &lt;code class=&quot;highlighter-rouge&quot;&gt;if&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;unless&lt;/code&gt; as modifiers after an expression, not requiring return in a method, &lt;code class=&quot;highlighter-rouge&quot;&gt;and&lt;/code&gt; vs. &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;amp;&amp;amp;&lt;/code&gt;, and on and on.&lt;/p&gt;

&lt;p&gt;More than that, Ruby adopted Perl’s design aesthetic that there should be multiple ways of doing things, and that code should be sort of natural language like (though I think that idea expresses itself very differently in the two languages).&lt;/p&gt;

&lt;h3 id=&quot;what-happened-to-perl&quot;&gt;What happened to Perl?&lt;/h3&gt;

&lt;p&gt;Lots of things but there are two big ones:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Although it was basically the most common language to create dynamic webpages in like, 2000, a higher-level framework never emerged, and Perl therefore lost share to Python and Ruby. Python and Ruby were also, in different ways, easier to use than Perl. I sometimes idly wonder why nobody came up with “Perl on Planes” or something, given that copying Rails was a big deal in 2007, and Perl had a longstanding association with the web.&lt;/li&gt;
  &lt;li&gt;Starting in the early 2000s the Perl community got fixated on a Perl version 6 that was going to be very not-backward compatible, and development on existing Perl languished for years while version 6 was in development. Perl 6 was eventually released as the language Raku.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;should-you-try-it&quot;&gt;Should you try it?&lt;/h3&gt;

&lt;p&gt;Y’know, when I started this, I would have said no, but brushing back up on the syntax makes me think it’s probably worth a go. It’s extremely well designed for its core task – writing a short script that does something fancy with text – and I don’t know that there’s anything quicker if you are a Perl expert.&lt;/p&gt;

&lt;p&gt;I also can’t leave this without at least mentioning my favorite piece of random Perl lore – &lt;a href=&quot;https://metacpan.org/dist/Lingua-Romana-Perligata/view/lib/Lingua/Romana/Perligata.pm&quot;&gt;Perligata&lt;/a&gt; or Perl in Latin. Not only did the authors here translate Perl’s functions into Latin, they also translated the grammar – in Latin word order in a sentence doesn’t matter, grammatical place is determined by a word ending. They made a version of Perl that works like that – the variable &lt;code class=&quot;highlighter-rouge&quot;&gt;next&lt;/code&gt; would be named &lt;code class=&quot;highlighter-rouge&quot;&gt;nexto&lt;/code&gt; as the receiver of an assignment but &lt;code class=&quot;highlighter-rouge&quot;&gt;nextum&lt;/code&gt; as the value, so &lt;code class=&quot;highlighter-rouge&quot;&gt;$last = $next&lt;/code&gt; could be written as &lt;code class=&quot;highlighter-rouge&quot;&gt;lasto da nextum&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;nextum da lasto&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;da lasto nextum&lt;/code&gt;. Just an amazing amount of commitment to the bit.&lt;/p&gt;

&lt;p&gt;Next up, we’ll look at Smalltalk.&lt;/p&gt;</content><author><name>Noel Rappin</name></author><category term="programming" /><category term="history" /><category term="ruby" /><category term="perl" /></entry><entry><title type="html">Programming Proverbs in 1975 and 2025</title><link href="https://noelrappin.com/2025/07/programming-proverbs-in-1975-and-2025/" rel="alternate" type="text/html" title="Programming Proverbs in 1975 and 2025" /><published>2025-07-30T00:00:00+00:00</published><updated>2025-07-30T00:00:00+00:00</updated><id>repo://blog.collection/_blog/2025-07-30-programming-proverbs-in-1975-and-2025.md</id><content type="html" xml:base="https://noelrappin.com/2025/07/programming-proverbs-in-1975-and-2025/">&lt;p&gt;As developers, we tend to think that our best practices are universal laws that we’ve discovered and which get refined over time. That’s true to an extent, but I think we underrate the ways our environment and technology shape what a best practice even is or what the best way to use a developers time might be. Looking at the past can help us calibrate what is and is not part of our environment.&lt;/p&gt;

&lt;p&gt;Recently, I was at a used book sale and I came across this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;IMG_1767.jpeg&quot; alt=&quot;The cover of Programming Proverbs by Henry F. Ledgard, from 1975&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It’s a book called &lt;em&gt;Programming Proverbs&lt;/em&gt; by Henry F. Ledgard and it was published in 1975, and apparently written even earlier than that. Naturally, I was intrigued, especially given the price (effectively zero, the sale was $1 for all the books you could fit in a bag). As far as I can tell, by the way, Ledgard is 82 and still alive. And he wrote a bunch of books, so I respect that.&lt;/p&gt;

&lt;p&gt;It’s paperback and square bound, not very thick, it looks like a chapbook. Honestly it looks like a lot of 70s counterculture media, kind of cheap, not super well typeset.&lt;/p&gt;

&lt;p&gt;I thought I would read this book and do a big compare and contrast as to how best practice advice has changed. As it happens, most of the book is worked out examples, and there’s really only one big way in which practices have changed that colors all the other advice in the book.&lt;/p&gt;

&lt;p&gt;To explain that, I need to give you the context of 1975. I’m not quite old enough to have professional memories of 1975, so this is a mixture of research and extrapolation. (If you think this was an excuse to research what programming was like in 1975… I can’t really argue with that.)&lt;/p&gt;

&lt;p&gt;There is basically no such thing as a home computer programmer – The Apple I is still two years away, and whatever the earlier generation of home hobbyists were doing is much more oriented around hardware.&lt;/p&gt;

&lt;p&gt;If you are programming computers for a living, you are likely working for the government, for a university, a research lab, or for one of a small but growing number of mostly large companies that are using mainframes for their business. If you are working at a company, you are probably writing Fortran or COBOL, researchers used a wider variety of languages. (The book mostly uses Algol 60 – I’m not completely sure how to reconcile that with the sources I found for programming language popularity.)&lt;/p&gt;

&lt;p&gt;C technically exists, but is only a couple of years old and has not really escaped Bell Labs yet. Object Oriented Programming, by and large, does not exist. Even conditionals like “if” are a little new-fangled. The oft-referenced “Goto Considered Harmful” letter is only a few years in the past (1968).&lt;/p&gt;

&lt;p&gt;The number of professional computer programmers was a fraction of what it is now. The statistics I looked up suggest that it was a tenth of what it is now, but I’m highly dubious, that sounds &lt;em&gt;way&lt;/em&gt; too high to me. I think the definition of what was considered a “programer” has change over time… my guess is the number of people professionally doing something like coding the kinds of logic that Ledgard is talking about is 1000x from 1975. (Note that a lot of the things he talks about are easily managed by spreadsheets now.)&lt;/p&gt;

&lt;p&gt;There’s no UI to speak of. You may not even have a screen. Emacs doesn’t exist. Vi doesn’t exist. There are editors, but they are line by line editors, and they don’t really do much beyond read and save keystrokes. You might not even have a keyboard. Unless I missed it, Ledgard doesn’t talk about what computers he’s working on, but he does make references to “typing or punching” in the programs (meaning using punch cards), and there are a lot of references to reading data off of punch cards.&lt;/p&gt;

&lt;p&gt;The problems you are solving are mostly batch processing. There’s data, there’s a known set of logic, the program ends and it produces output to a terminal or a teletype or something. Debugging could mean trying the entire program again. Not only does this potentially mean messing around with punch cards, but it also is very likely that you are sharing an oversubscribed computer and you can’t get access to just run your program again, you need to schedule time.&lt;/p&gt;

&lt;p&gt;It’s not much of a stretch to say that literally everything about my day-to-day work as a coder is different from what Ledgard was writing about.&lt;/p&gt;

&lt;p&gt;What follows is a mix of advice that could have been written yesterday crossed with advice that is extremely specific to that particular time and place. And yet the similarities of language and terminology kind of hide how different Ledgard’s working day was than mine is.&lt;/p&gt;

&lt;p&gt;The book exists to advocate for what Ledgard calls “Top-Down” programming, and I was quite sure that I knew what he meant by that. I was so sure that I actually misread the text at first.&lt;/p&gt;

&lt;p&gt;Here’s what Ledgard means by Top-Down programming:&lt;/p&gt;

&lt;p&gt;Step one is to have an exact problem definition. Ledgard stresses this point: “It is senseless to start any program without a clear understanding of the problem”. In the part of the book that is actually programming proverbs the very first proverb is to define problems exactly and completely.&lt;/p&gt;

&lt;p&gt;Right off the bat you can see this is going to have problems being applied to current programming practice. Ledgard’s process is almost by definition a waterfall process (oh – the term “waterfall” hadn’t been popularized yet). One of the salient features of attempts to deal with software process from about 1990 on was the understanding that it was not always possible or desirable to have an exact problem definition at the beginning of the process, but that you needed to still be able to build software. The subtitle of the original XP book, from 2000, is “Embrace Change”.&lt;/p&gt;

&lt;p&gt;But fine, the problems being dealt with here are both amenable to exact definitions and small enough such that if the problem changes, just redoing the code from scratch is viable. This is an ecosystem change, as computer programming has been asked to do more and more complex things, starting with an exact, permanent problem definitions becomes less and less viable.&lt;/p&gt;

&lt;p&gt;We then get a description of the top down method. I’m paraphrasing here, and I think I’ve got it, but I’m not 100% sure.&lt;/p&gt;

&lt;p&gt;You start with what we would now call a pseudocode implementation of the entire program, I quote…&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The programmer initially uses expressions… in English that are relevant to the problem solution, even though the expressions cannot be directly translated into the target language.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then we go into a loop:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Pick a part of the code that is still too abstract and refine it to the next level of abstraction down, postponing details to lower levels as needed.&lt;/li&gt;
  &lt;li&gt;Ensure that the program is correct, the idea here is that individual sections can be written and validated independently of each other.&lt;/li&gt;
  &lt;li&gt;Continue to refine and debug until you have completed the program.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I think I’ve got this correct, but I’m not sure, even though the book explains it at least twice.&lt;/p&gt;

&lt;p&gt;Let me put it this way – looking at that set of steps with your 2025 programmer brain and knowing you start with pseudocode, when would you start writing actual code?&lt;/p&gt;

&lt;p&gt;I’m going to guess that you had the same reaction I did. Cued by the term “debug” and the idea of ensuring correctness, I initially assumed that you’d convert the pseudocode to real code immediately. This makes it similar to a structure I’ve used and called “top-down” before: I write the highest level function, that top-level function calls a bunch of other functions, then I write those functions and so on.&lt;/p&gt;

&lt;p&gt;But that’s not the process as Ledgard presents it. In the examples in the book, the programmer goes through the entire process in pseudocode until the program is complete and only then does the programmer translate it to an actual programming language. In fact, in the examples in the book, the pseudocode is often translated into multiple languages and they pick which language to use after seeing the code in all three languages.&lt;/p&gt;

&lt;p&gt;And yes, this means that Ledgard expects the programmer to debug pseudocode. There’s an explicit example in the book where pseudocode is debugged, it’s something like a formal proof of correctness.&lt;/p&gt;

&lt;p&gt;When I realized this, my brain exploded.&lt;/p&gt;

&lt;p&gt;I started to think about how the superficial similarities between 1975 and 2025 made me think one thing was going on, but actually the differences in our environments mean that in some ways, Ledgard and I are doing extremely different things, both called “programming”.&lt;/p&gt;

&lt;p&gt;In Ledgard’s world, computer time is expensive – much more expensive than programmer time. So it makes sense for a programmer to do pencil-and-paper work to debug a program that doesn’t even exist so that computer time is not wasted on successive improvements of code that doesn’t work.&lt;/p&gt;

&lt;p&gt;In my world, programmer time is much more expensive than computer time, so it makes sense for me to offload testing, syntax checks, and whatever to the computer.&lt;/p&gt;

&lt;p&gt;I’m not sure when the inflection point happened between computer and developer time, but I’d guess it was about 1979 and that the first group to experience an environment where computer time was significantly less expensive than programmer time. was the Xerox PARC team working with Xerox Stars – there are accounts of that team feeling guilty about leaving their computers idle overnight.&lt;/p&gt;

&lt;p&gt;The relative value of computer and programmer time has actually continued to change in my lifetime – when I started coding in earnest, developer tools were one of the most resource intensive programs you could run. This is no longer true. All kinds of little nice code management features that are in basically any 2025 tool, like syntax coloring or anything an LSP server does are all things that would have been difficult or impossible when I started coding for real in the late 1980s (at least, not without creating your own operating system, like Smalltalk did).&lt;/p&gt;

&lt;p&gt;My point is that the practices that made sense given the problems and cost structures in 1975 don’t all make sense now.&lt;/p&gt;

&lt;p&gt;My follow up point is that, wherever LLMs and the like land, we are in the middle of a rapid change in the relative cost of different kinds of programmer tasks. A lot of boilerplate tasks that were relatively expensive pre-LLMs either are right now or will shortly be significantly less expensive.&lt;/p&gt;

&lt;p&gt;Even if you are – like me – a little dubious that the entire development stack is headed for a 10x or more speed improvement, it seems to me pretty likely that parts of the process that are boilerplate or amenable to “just good enough” code (like, potentially, testing) are already at or near this level of improvement.&lt;/p&gt;

&lt;p&gt;I’m not sure I’ve really seen a good reckoning with what that cost/benefit change is going to mean for individual developer practice, or team developer practice.&lt;/p&gt;

&lt;p&gt;For instance, it might once again be feasible to ask an LLM to render a problem in multiple languages and choose the best one based on performance or whatever. It might be feasible to generate the same code in multiple designs to see which one you like the best.&lt;/p&gt;

&lt;p&gt;Where this goes is anybody’s guess, and the effect on developers as a whole might be weirder than we think (for instance, programming might have &lt;a href=&quot;https://en.wikipedia.org/wiki/Baumol_effect&quot;&gt;Baumol effects&lt;/a&gt;, where the cost of human labor rises even when the productivity of the human labor does not). Not that I understand Baumol effects well enough to predict.&lt;/p&gt;

&lt;p&gt;I’m glad I picked this book up. I really like the history of programming, and I did learn something, and not what I expected, which was great.&lt;/p&gt;</content><author><name>Noel Rappin</name></author><category term="programming" /><category term="history" /></entry><entry><title type="html">What Do I Think I Think About LLMs</title><link href="https://noelrappin.com/2025/05/what-do-i-think-i-think-about-llms/" rel="alternate" type="text/html" title="What Do I Think I Think About LLMs" /><published>2025-05-28T00:00:00+00:00</published><updated>2025-05-28T00:00:00+00:00</updated><id>repo://blog.collection/_blog/2025-05-28-what-do-i-think-i-think-about-llms.md</id><content type="html" xml:base="https://noelrappin.com/2025/05/what-do-i-think-i-think-about-llms/">&lt;p&gt;Every time I think the AI frenzy has peaked, it peaks again. Writing about coding these days feels like Jimmy Stewart dancing on the edge of a floor that’s rapidly receding under him.&lt;/p&gt;

&lt;p&gt;I had a draft of this that started with five or six capsule stories of interactions with LLMs for coding purposes, some saving incremental time, some being wrong, some even being right.&lt;/p&gt;

&lt;p&gt;Then I realized that I probably shouldn’t be that detailed about work stuff, but more importantly, you likely have all these stories too. You’ve seen useful autocorrect, and you’ve seen the LLM be confidently wrong and you’ve seen them be confidently right.&lt;/p&gt;

&lt;p&gt;My point is, there’s a lot of ways to feel about LLMs as coding tools. I’ve been holding on to this post for three weeks and I’ve changed my mind at least twice.&lt;/p&gt;

&lt;p&gt;In two weeks, I’ll probably cringe at this.&lt;/p&gt;

&lt;p&gt;Here’s a few things I think I think…&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reading SF did not prepare me for this&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I’ve been reading science fiction about AI basically since I could read, and I’ve never seen a treatment of AI that captures how weird and scammy parts of this AI world seem, even as parts of it seem to be delivering value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I’m getting plagiarized&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Part of what makes me really ambivalent about LLM tools is that I know for a dead-certain fact that my 20 year career in technical writing is a (admittedly small) part of the training set for these tools. I don’t like that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Both extremes of the argument about LLMs seem wrong in my experience&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Team “Let’s push AI” wants to convince me that the existing tools are good enough to be an order of magnitude improvement on my productivity. That’s just flat out not true in my experience, though I know some people have reported this and, I guess, good for them. Team “burn it all down” wants to convince me that these tools have no value at all. That’s also not true in my experience. There’s a lot of things LLMs aren’t very good at, but I’ve gotten code review advice, and sometimes good advice on bugs. I’ve had it generate tests, and worked with it to create code – my style is weird enough that it takes some back and forth. The best of the “fancy autocomplete” has gotten good enough to be an incremental improvement in my day. I miss fancy autocomplete when I don’t have it. I don’t yet miss LLM chat when I don’t have it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is already much further along than I expected to see in my lifetime&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I always thought that computer generated code was coming, but I thought I’d be safely retired. The image generation is well beyond what I would have expected to see in my lifetime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I’m very dubious of self-reports of time saved&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In aggregate, are the LLMs saving me time? Incrementally, probably. Autocomplete has gotten good enough to pretty clearly be an incremental time savings. Code generation feels like an incremental improvement sometimes.&lt;/p&gt;

&lt;p&gt;And yeah, I’ve seen the “I vibe-coded this and now I have paying customers” people, and I dunno. I do not yet have the confidence to run pure LLM code through anything that needed security or was touching money.&lt;/p&gt;

&lt;p&gt;What is true of my LLM coding adventures that the nature of the coding task changed. There was much less of my typing in code to generate data, the tool did most of that. There was much more pleading with the thing not to change unrelated code.&lt;/p&gt;

&lt;p&gt;People are notoriously bad at evaluating subjective time across different kinds of thought processes. Did it seem faster because there was less busy work? Did it feel longer because I was basically arguing with the AI The whole time? I’m genuinely not sure, and the counter-factual of not using LLMs is hard to estimate.&lt;/p&gt;

&lt;p&gt;Way back when I was in ed-tech, we used to say that speeding up a user task 10x fundamentally changed the task. The 90s version of this was, say, Apple’s Graphing calculator, which allowed you to “browse” graphs in a way that wasn’t feasible before.&lt;/p&gt;

&lt;p&gt;So if I could do a 10 hour coding task in 1 hour (which, to be clear, is nowhere near what I’m seeing), then I’ve really fundamentally changed the task – to expand the analogy, I could easily browse different coding structures to try them out before I picked one. My sense is that this is potentially true for prototyping, which does seem to be allowing people to explore a design space more completely.&lt;/p&gt;

&lt;p&gt;I will say this – two years ago I asked an LLM to code up the NFL tiebreaker process and it flopped – for example, it couldn’t reliably keep track of how to count ties. I asked a couple of weeks ago, and I got a full design, with a question of how to roll out the implementation step by step. I haven’t taken it to the final steps, so I’m not sure how hard the last details will be to nail down. But the skeleton structure came in very fast and very detailed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can’t tech your way out of non technical problems&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We already have automation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I’ll tie these together, because I think they are both limitations of what the tools can do. An LLM is not going to fix communication problems at your organization, and if your developers don’t want to learn a thing, an LLM is not magically going to make them learn it. The LLM may make them able to fake it for a while, this doesn’t seem like a great idea to me…&lt;/p&gt;

&lt;p&gt;Meantime, I keep seeing people proposing LLMs tackle normal automation tasks, which I don’t quite get. Like, I feel that tying my ticket system to GitHub is a problem I don’t need an LLM to solve. Or do I? This is the place where I really do feel like there’s something that other people are seeing that I’m not seeing. There’s a whole world of agents that I sort of dimly see what the fuss is about but have not experienced value yet.&lt;/p&gt;

&lt;p&gt;The pushback I get on this is that the LLMs are faster than writing bash scripts, which brings me back to the self-reporting issue and the question of are they, really? The examples I’ve seen that work seem to have taken enough back and forth with the LLM that I’m genuinely not sure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LLMs make non-coding tasks feel like coding&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After hearing people talk about using LLMs to generate PR summaries or internal communication, I had the thought that one reason why developers like LLMs for this is that it turns non-coding communication tasks into something that feels like coding.&lt;/p&gt;

&lt;p&gt;Whether this is a good thing or not, I’m not sure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conversely LLMs can make coding tasks feel like non-coding&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Obviously one of the other selling points is that powerful coding tasks are now potentially available to non-coders. Vibe-coding is just “coding that doesn’t feel like coding”.&lt;/p&gt;

&lt;p&gt;And like, I don’t have a problem with tools solving problems for people. I follow a guy who uses LLMs to write Python scripts that help him manage work spreadsheets. Seems fine to me.&lt;/p&gt;

&lt;p&gt;That said, on some of the back and forth I’ve had with LLMs, I can feel my brain shift out of coding mode watching the LLM go back and forth.&lt;/p&gt;

&lt;p&gt;Honestly, it didn’t feel great.&lt;/p&gt;

&lt;p&gt;And I did have the case of switching back into coder brain and looking at the test and realizing, “that’s way more complicated than it needs to be”.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The inefficiency is load bearing and you can’t make a spark without friction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I pulled the first phrase out of a &lt;a href=&quot;https://bsky.app/profile/opinionhaver.bsky.social/post/3lbxqkgxgnc2d&quot;&gt;Bluesky post on media&lt;/a&gt; and I think it explains a lot of what’s going on in a lot of directions.  I keep hearing that LLMs will remove friction, and I think, things without friction are really… slippery, which is a metaphor and maybe not a metaphor?&lt;/p&gt;

&lt;p&gt;Sometimes the journey is the destination, and sometimes the act of summarizing or explaining your code is valuable for learning. Things that feel like time sinks can be valuable.&lt;/p&gt;

&lt;p&gt;You can take this too far – sometimes grunt tasks are just grunt tasks. But I think it’s worth trying to be careful about what you are trying to accomplish with a tool both in the immediate term and the longer term.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caring Matters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I’m working on a rule of thumb here, which is that the usefulness of LLMs in generating code is inversely proportional to how much I care about the code.&lt;/p&gt;

&lt;p&gt;One-off script in a language I don’t know very well that is not going to be reused – don’t really care, LLM can probably help a lot.&lt;/p&gt;

&lt;p&gt;Throw-away prototype? I literally don’t care about the code itself. LLM probably helpful.&lt;/p&gt;

&lt;p&gt;End-to-end test of a system that I already have unit tested? I care that it does what it says, but the style is pretty constrained. LLM could be helpful.&lt;/p&gt;

&lt;p&gt;Writing code in a well-constrained piece of business logic? Depending on the constraints, I’m probably arguing about style and long-term issues, so I’m thinking helpful to start and then less so.&lt;/p&gt;

&lt;p&gt;Business critical function under web-scale load? I’m going to want to look over that carefully.&lt;/p&gt;

&lt;p&gt;Another way of looking at it is that as the code becomes more critical, the typing part that is what the LLM most clearly speeds up becomes less and less of the overall task. (You can get LLMs to help with design, but that takes more back and forth, so it’s not as much of a time saver.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I don’t know what to think about LLM generated code quality&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Anecdotally, people around me seem to feel that the LLMs generate worse Ruby code than other languages. I’m a little dubious (of the part where the code quality is better in other languages).&lt;/p&gt;

&lt;p&gt;I built out (most of) the NFL tiebreaker program, and the code was… serviceable. Like, it probably works, but the factoring of it is a little odd. In general, it has the feel of a very brute-force solution, a little overly complex, but one or two things I might not have thought of. (I’ve found “overly complex” to be a common problem with LLM code.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I’m pretty sure the LLM shouldn’t generate both the code and the tests&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At least not for code you care about.&lt;/p&gt;

&lt;p&gt;This is maybe a subset of “don’t commit code you don’t understand” or the IBM “A computer can not be held responsible” meme.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try to frame the ways this expands your reach&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Where I’ve been successful with LLM’s it’s been using them to expand what I can do – “what are the design options here”, “how can I observe the causes of this error”, “How can I tell what this error message might mean”. When I’ve asked it for things that contract what I do “generate a test for this code”, “write this function”, I’ve had mixed success, I usually get the code, but there are often problems problems.&lt;/p&gt;

&lt;p&gt;I had an interesting experience with “find examples in this code base where I could use Ruby pattern matching”, where it did find legit examples, but the explanations were… weird?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sometimes this sounds like previous generations language debates&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And coding has a rich history of The Olds (which I clearly am by now) saying that The Youngs have it too easy. That’s assembly to C and it’s C to Java, and it’s Java to Ruby.&lt;/p&gt;

&lt;p&gt;If you go back to the rise of C, you can find assembly language programmers lamenting the loss of granular memory management. If you go back to the rise of Python/Ruby, you can &lt;em&gt;definitely&lt;/em&gt; find C programmers lamenting the loss of manual memory management. Me currently lamenting all the things you lose from vibe coding – is it the same thing? Am I the old person yelling at the tides?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Kent said&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I quote this from Kent Beck all the time: “The value of 90% of my skills just dropped to $0. The leverage for the remaining 10% went up 1000x. I need to recalibrate.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We need to be super careful about what this does to the pipeline&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Anecdotally, LLMs are already affecting hiring of junior devs. This is, of course, a problem because junior devs are the leading source of senior devs.&lt;/p&gt;

&lt;p&gt;Anecdotally, I have to imagine that this just feels super-weird to mid-level devs who may or may not have developed enough taste to evaluate the LLM output and who likely feel they have to get great at the tools, like, yesterday or be Left Behind.&lt;/p&gt;

&lt;p&gt;I mean, ever since Y2K, I’ve always imagined myself coming out of retirement in for One Last Job. But I always assumed it’d be the Unix 2038 issue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where the ceiling is is going to matter&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I don’t know how much better the LLMs are going to get in the next few years. The improvement in the last year or so has been noticeable. Even if the models don’t improve, they are likely to get cheaper, faster, and more accessible.&lt;/p&gt;

&lt;p&gt;But it’s going to make a big difference if the ceiling is “able to competently write small tools” versus “able to manage large systems”, and I just don’t know where the endpoint is.&lt;/p&gt;

&lt;p&gt;I don’t really have a conclusion to this, other than it will probably all be invalid in, like two weeks.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Comments? I’ve had to disable them on noelrappin.com, if you want to discuss this post, the URL will be &lt;a href=&quot;https://buttondown.com/noelrap/archive/what-do-i-think-i-think-about-llms/&quot;&gt;https://buttondown.com/noelrap/archive/what-do-i-think-i-think-about-llms/&lt;/a&gt;&lt;/p&gt;</content><author><name>Noel Rappin</name></author><category term="llm" /><category term="coding" /></entry><entry><title type="html">2024 Books Recap (Finally)</title><link href="https://noelrappin.com/2025/05/2024-books-recap-finally/" rel="alternate" type="text/html" title="2024 Books Recap (Finally)" /><published>2025-05-10T00:00:00+00:00</published><updated>2025-05-10T00:00:00+00:00</updated><id>repo://blog.collection/_blog/2025-05-10-2024-books-recap-finally.md</id><content type="html" xml:base="https://noelrappin.com/2025/05/2024-books-recap-finally/">&lt;p&gt;So, this is wildly late. So late that it mocks the very concept of an end-of-year recap. And honestly, if I didn’t like going back to these things and re-reading them, I probably wouldn’t have finished it.&lt;/p&gt;

&lt;p&gt;Couple of quick notes before getting to it:&lt;/p&gt;

&lt;p&gt;The new thing this year is that I used &lt;a href=&quot;https://app.thestorygraph.com&quot;&gt;The StoryGraph&lt;/a&gt; to rate all the books right after I finished them. You’d think that’d make it easier to get this list out, but apparently not. I’m not posting my actual star ratings because my actual star ratings are a little weird. (I can’t seem to give any book I actually finish less than a 3.5 out of 5, and so the whole thing is kind of off). Anyway, because of StoryGraph, I can say that reading broke down to about one-third fantasy, a quarter SF, a quarter tagged as romance, about one-fifth mystery and a smattering of other things (with the understanding that some books will tag as more than one of these).&lt;/p&gt;

&lt;p&gt;Also, it’s been more than 18 months since I read some of these, some of the notes are a little spare.&lt;/p&gt;

&lt;p&gt;One year, I’ll get my act together to put cover images on this thing. This is not that year.&lt;/p&gt;

&lt;p&gt;As usual, don’t take the rankings too seriously, I liked all of these, and I hope this lets you know if you would like them too.&lt;/p&gt;

&lt;h2 id=&quot;for-the-sake-of-grouping-call-this-group-three&quot;&gt;For the sake of grouping, call this group three…&lt;/h2&gt;

&lt;p&gt;These are mostly a 4.25 on my weird StoryGraph star scale, plus I think a couple lower ones that I wanted to write about.&lt;/p&gt;

&lt;h3 id=&quot;37-butcher-and-blackbird-by-brynne-weaver&quot;&gt;37: &lt;a href=&quot;https://amzn.to/41VDIWE&quot;&gt;Butcher and Blackbird&lt;/a&gt; by Brynne Weaver&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Why did I read this?&lt;/strong&gt; I think it was a BookTok thing&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; A rom-com between two serial killers, what could go wrong?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do The Math:&lt;/strong&gt; Harry and Sally meet Dexter?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommended If You Like:&lt;/strong&gt; Trigger Warnings… The book opens with a list as long as your arm.&lt;/p&gt;

&lt;p&gt;Let’s start this off with a bang. Oh… pun not intended.&lt;/p&gt;

&lt;p&gt;This is very much a Not For Everyone work.&lt;/p&gt;

&lt;p&gt;Our main leads are both serial killers who like to kill other serial killers. Sure. They meet-cute when he rescues her from captivity from a different serial killer. Double sure. They become friends, having admired each other’s work, and set out on regular challenges where they try to discover and kill a particular serial killer. Triple sure. So it’s your standard friends to lovers story where they happen to share a hobby…&lt;/p&gt;

&lt;p&gt;Some of you have noped out of this one already. Fair enough.&lt;/p&gt;

&lt;p&gt;Anyway, eventually they get together romantically, with a strong, um, kink component…&lt;/p&gt;

&lt;p&gt;Sometimes, I say about a book that if anything about the book seems compelling you should try it. This is kind of the opposite… if anything about the above makes you think that you are going to be squeamish about this, you probably will be.&lt;/p&gt;

&lt;p&gt;That said, I mostly liked it – I liked the first half more than the second half, it’s got more of the dark and weird banter. (This is of a couple of rom-coms I read this year that lost momentum once the characters get together because the characters just keep wanting to hook up and, while that can be fun, it does slow the story down for a while. I guess it depends on what you are hoping to get out of the book…) I did not feel like I needed to stick around for the next book in the series.&lt;/p&gt;

&lt;h3 id=&quot;36-charlotte-illes-is-not-a-teacher-by-katie-siegel&quot;&gt;36: &lt;a href=&quot;https://amzn.to/43cOA2F&quot;&gt;Charlotte Illes Is Not a Teacher&lt;/a&gt; by Katie Siegel&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch&lt;/strong&gt; Encyclopedia Brown grows up.&lt;/p&gt;

&lt;p&gt;Sort of noted without comment. I like this series about a former teenage detective now somewhat aimless 20-something, but I don’t really have a lot to say about it. One thing that’s fun about this one is that Charlotte winds up going back to the school where she was a kid detective and learns, somewhat to her surprise, that her kid impressions about the faculty and what the faculty thought about her were not completely accurate.&lt;/p&gt;

&lt;h3 id=&quot;35-a-novel-love-story-by-ashley-poston&quot;&gt;35: &lt;a href=&quot;https://amzn.to/4kT0t4M&quot;&gt;A Novel Love Story&lt;/a&gt; by Ashley Poston&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; It’s Shmigadoon, but for romance novels&lt;/p&gt;

&lt;p&gt;I suppose it was inevitable that somebody would do this. Our main character goes off the beaten path and winds up in the fictional town that is the setting created by her favorite romance author. This author died with the series unfinished, so there are a few romantic pairings in the group that are unsettled. Including, I guess, our main character, who immediately gets in one of those classic do I or don’t I like him romance things with the bookstore owner, who turns out to be the only other person that knows the town is fictional.&lt;/p&gt;

&lt;p&gt;If you are a “why does the fictional town exist” kind of person, this isn’t the book for you. If you can turn that switch off, this is one of those satires that is also a good example of the thing, which I have a soft spot for.&lt;/p&gt;

&lt;h3 id=&quot;34-the-bright-sword-by-lev-grossman&quot;&gt;34: &lt;a href=&quot;https://amzn.to/3Pk41P6&quot;&gt;The Bright Sword&lt;/a&gt; by Lev Grossman&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; What if you showed up at the round table just a bit too late…&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do The Math:&lt;/strong&gt; In some ways, it’s King Arthur + two weeks. In another sense, it’s King Arthur + modern sensibility&lt;/p&gt;

&lt;p&gt;The setup here is incredibly clever. Our main, but not only, viewpoint character, is a strapping young local lad who escapes a bad situation to run off to join the Round Table. Sadly, he arrives two weeks after the story we know has ended with Arthur’s death. Most of the, shall we say, A-Team, is either dead or actively working against Camelot. What’s left are basically the characters who are comic relief in other parts of the story, and they are trying to rebuild, or at least prevent a total collapse.&lt;/p&gt;

&lt;p&gt;Grossman has embedded in the story a more complex take on the competing politics trying to fill the power vacuum, and also a more complex set of identities available to the remaining knights. Since it is effectively an aftermath story, it can live and breathe on its own without really having to worry about contradiction with the earlier lore. I’m not really much for Arthur stories (I read this because of the author, not the topic), but I liked this one.&lt;/p&gt;

&lt;h3 id=&quot;33-the-book-of-love-by-kelly-link&quot;&gt;33: &lt;a href=&quot;https://amzn.to/402fhV9&quot;&gt;The Book of Love&lt;/a&gt; by Kelly Link&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; I mean, the elevator pitch is that it’s a full length novel by Kelly Link…&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommended If You Like:&lt;/strong&gt; beautiful sentences, magical realism, journeys rather than destinations…&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Award&lt;/strong&gt; 2025 Nebula Nominee for Best Novel&lt;/p&gt;

&lt;p&gt;Link is an extremely successful short story writer of horror/magic realism – wikipedia describes it as “slipstream” which is short for “we don’t know how to describe it”. Her short stories are known for her particularly gorgeous writing and for a very well honed sense of being unsettling.&lt;/p&gt;

&lt;p&gt;In her first novel, a group of teenagers who disappeared/died mysteriously are brought back to life for reasons unknown, and are given a group of tasks to perform, with the strong implications that winners will get to stay alive and losers… won’t.&lt;/p&gt;

&lt;p&gt;The book then goes on for 600+ pages back and forth through multiple viewpoints.  It has particularly gorgeous writing and a very well honed sense of being unsettling. And I’ll say this – it’s incredibly good at setting a mood, and it’s tightly plotted – everything happens for a reason and all the reasons are exposed. It’s long though – the plot is complex, and there’s a lot of stuff to get established before things pay off. When it clicks, though, it really, really clicks.&lt;/p&gt;

&lt;h3 id=&quot;32-christa-comes-out-of-her-shell-by-abbi-waxman&quot;&gt;32: &lt;a href=&quot;https://amzn.to/4d0poQh&quot;&gt;Christa Comes Out of Her Shell&lt;/a&gt; by Abbi Waxman&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; What if your dad was, like, Steve Irwin, only he faked his death?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommended If You Like:&lt;/strong&gt; Linda Holmes or Emily Henry but funnier and with less romance&lt;/p&gt;

&lt;p&gt;You can kind of trace the amount to which I’ve added romance and romance-adjacent books through the years (though I read a lot of romance books that score just below the cutoff to get listed here). Anyway, Abbi Waxman writes romance adjacent books (okay, fine, the marketing name was probably “chick-lit”) that are quirky and have a genuinely funny narrative voice. Like, her narration causes me to laugh multiple times a book, which is pretty rare.&lt;/p&gt;

&lt;p&gt;This book, about a researcher who would really just like to stay on the literal tiny island where she studies animal behavior but has to face her famous father when he reveals that he’s not actually dead, is all those things. I liked it.&lt;/p&gt;

&lt;h3 id=&quot;31-death-at-morning-house-by-maureen-johnson&quot;&gt;31: &lt;a href=&quot;https://amzn.to/434fJDX&quot;&gt;Death at Morning House&lt;/a&gt; by Maureen Johnson&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Award&lt;/strong&gt; Edgar Award nominee for best YA mystery novel&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommended If You Like:&lt;/strong&gt; This is sort of a Love/Like book with the Truly Devious series, in that if you loved those books, you will probably like this one.&lt;/p&gt;

&lt;p&gt;This, like the Stevie Bell books, happens in two timelines, a historical mystery from the 1930 and a present day mystery that is probably influenced by the history. Unlike the Stevie Bell books, our main character isn’t a detective, she’s part of the staff of teens giving tours at Morning House, a mansion on one of the Thousand Islands. It’s a good place for her to get over the fact that she accidentally set a house on fire during a date with her crush.&lt;/p&gt;

&lt;p&gt;Then, since this is a mystery novel, somebody dies.&lt;/p&gt;

&lt;p&gt;This book has less of a whodunit feel and… closer to a haunted house feel in a way, it’s more vibes-based than the Stevie Bell mysteries. The sense of place is really strong.&lt;/p&gt;

&lt;h3 id=&quot;30-echo-of-worlds-by-m-r-carey&quot;&gt;30: &lt;a href=&quot;https://amzn.to/3EUxZr7&quot;&gt;Echo of Worlds&lt;/a&gt; by M. R. Carey&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; It’s the sequel to &lt;em&gt;Infinity Gate&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Boy, did I want to like this one more than I did. It’s a sequel to Infinity Gate, which is best described as an epic space opera, where instead of going out into space, all the planets are different multiverse versions of Earth. It’s a great premise, and the first book sets up a really neat situation.&lt;/p&gt;

&lt;p&gt;This book, while it has a lot of interesting stuff, to my mind kind of squanders the most interesting parts of the first book, there’s less of the really weird alternate worlds, and I want to say that it kind of breaks up the team that was built up in the first book, though honestly I don’t really remember. Anyway, still worth it, but I wanted it to shoot the moon and it just didn’t quite.&lt;/p&gt;

&lt;h3 id=&quot;29-how-to-solve-your-own-murder-by-kristen-perrin&quot;&gt;29: &lt;a href=&quot;https://amzn.to/3ShfQqy&quot;&gt;How to Solve Your Own Murder&lt;/a&gt; by Kristen Perrin&lt;/h3&gt;

&lt;p&gt;I really need to do a better job of remembering book I read nine months ago.&lt;/p&gt;

&lt;p&gt;Our heroine is called to a small English village because of the death of her great-aunt. Her great aunt received a fortune in 1965 that she would be murdered, and spent the rest of her life trying to preemptively solve the murder. Now she’s dead – murdered, as it happened – and our heroine needs to figure out what happened. It’s a pretty solid mystery premise and a pretty good execution.&lt;/p&gt;

&lt;h3 id=&quot;28-lady-eves-last-con-by-rebecca-fraimow&quot;&gt;28: &lt;a href=&quot;https://amzn.to/41Q4vlP&quot;&gt;Lady Eve’s Last Con&lt;/a&gt; by Rebecca Fraimow&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; Leverage in Space!&lt;/p&gt;

&lt;p&gt;I want Leverage in Space to happen so badly that I’ll read nearly everything that comes close to that description.&lt;/p&gt;

&lt;p&gt;Our main character, Ruth, is a small time con artist working to avenge her sister’s breakup with a rich nerd. Naturally, she does this by posing as a debutante and breaking his heart in turn. Things get complicated when his more-on-the ball older sister starts to be suspicious and more complicated as Ruth starts to fall for her. This one is fun, it’s an SF setting that has a lot in common with your Regency-style romances what with the nobles and all, it’s got some Jewish rep – Ruth is Jewish and breaks into yiddish from time to time. I had fun.&lt;/p&gt;

&lt;h3 id=&quot;27-a-ruse-of-shadows-by-sherry-thomas&quot;&gt;27: &lt;a href=&quot;https://amzn.to/4iUwMi4&quot;&gt;A Ruse of Shadows&lt;/a&gt; by Sherry Thomas&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; Book 8 of the Lady Sherlock Series&lt;/p&gt;

&lt;p&gt;This is a series where I love the characters and most of the individual book plots but find the larger cross-book plot to be kind of impenetrable. This one has a little less to do with the overarching plot than some of the others, so the characters are back in the forefront.&lt;/p&gt;

&lt;h3 id=&quot;26-why-we-love-football-by-joe-posnanski&quot;&gt;26: &lt;a href=&quot;https://amzn.to/4daVrNG&quot;&gt;Why We Love Football&lt;/a&gt; by Joe Posnanski&lt;/h3&gt;

&lt;p&gt;A countdown of 100 memorable football moments. Posnanski is a great writer, and a lot of the individual essays here are great. I don’t care as much about football as I do about baseball, but he nailed the Walter Payton essay and that pretty much was all I needed. If you didn’t grow up on Walter Payton, you’ll probalby be looking for a different essay here.&lt;/p&gt;

&lt;h3 id=&quot;25-the-wings-upon-her-back-by-samantha-mills&quot;&gt;25: &lt;a href=&quot;https://amzn.to/44hu41X&quot;&gt;The Wings Upon Her Back&lt;/a&gt; by Samantha Mills&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Do The Math&lt;/strong&gt; Some Desperate Glory - spaceships + gods + wings&lt;/p&gt;

&lt;p&gt;This is where doing these so late really hurts, because I wish I remembered this one in more detail. As the book starts, our main character has been unceremoniously exiled from her elite troupe of warriors who have mechanical wings fused to their backs. The world is a fantasy world with gods, and we eventually learn that she’s been a part of this warrior… troupe since she was a teenager, and that the higher-ups have been getting increasingly militaristic and fascist. She’s turned a blind eye to it, which gets much more challenging after she’s kicked out.&lt;/p&gt;

&lt;p&gt;Anyway, this is a good book, with some interesting world-building and some definitely intended resonances with our actual timeline. It’s a pretty good double feature with &lt;em&gt;Some Desperate Glory&lt;/em&gt;, with similar arcs of a character increasingly coming to understand what she’s pledged her life to.&lt;/p&gt;

&lt;h3 id=&quot;24-a-city-on-mars-by-kelly--zach-weinersmith&quot;&gt;24: &lt;a href=&quot;https://amzn.to/4iIBTRJ&quot;&gt;A City on Mars&lt;/a&gt; by Kelly &amp;amp; Zach Weinersmith&lt;/h3&gt;

&lt;p&gt;Non-fiction, basically covering all the, shall we say, less headline grabbing things that would have to happen to colonize Mars. Legal issues, logistical issues, medical issues, all the stuff that SF (often) overlooks because it’s hard to make into a story, and all the stuff that the “colonize Mars” crowd ignores because it’s hard or unsolvable and not as much fun as big rockets.&lt;/p&gt;

&lt;p&gt;It’s a fun book, especially if you don’t have much invested in eventually going to Mars (frankly, Mary Roach’s &lt;em&gt;Packing For Mars&lt;/em&gt; disabused me of this possibility years ago).&lt;/p&gt;

&lt;h3 id=&quot;23-dreadful-by-caitlin-rozakis&quot;&gt;23: &lt;a href=&quot;https://amzn.to/3YrVPRH&quot;&gt;Dreadful&lt;/a&gt; by Caitlin Rozakis&lt;/h3&gt;

&lt;p&gt;Wait for it…&lt;/p&gt;

&lt;h3 id=&quot;22-someone-you-can-build-a-nest-in-by-john-wiswell&quot;&gt;22: &lt;a href=&quot;https://amzn.to/4bZLS3A&quot;&gt;Someone You Can Build a Nest in&lt;/a&gt; by John Wiswell&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Award For:&lt;/strong&gt; 2025 Nebula and Hugo Award Nominee for Best Novel&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch&lt;/strong&gt;: What if Odo and Kira, but in fantasy, and Odo wants to eat Kira and/or implant eggs in her…&lt;/p&gt;

&lt;p&gt;There was a huge spate of books this year that were fantasy from the point of view of a misunderstood or nominally villainous character – these two, plus&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;How to Become the Dark Lord and Die Trying&lt;/em&gt;, which just missed the list&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Long Live Evil&lt;/em&gt;, which we will talk about further up&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In &lt;em&gt;Dreadful&lt;/em&gt;, our hero wakes up with no memories from what appears to be some kind of magic spell failure. He discovers that he was an Evil Wizard, and decides that he does not want to be an Evil Wizard anymore.&lt;/p&gt;

&lt;p&gt;But you can’t just quit evil wizarding without attracting the ire of the other evil wizards, so what follows is comic farce as our hero attempts to convince everybody that he’s still a powerful wizard while trying to stop the evil plan that he no longer remembers. It’s fun, a little breezy, but fun.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Nest&lt;/em&gt; has a little more body horror to go with the monster. The main character of this book is a shapeshifter that eats people, uses their organs to build her body and/or lay her eggs inside them. In normal fantasy parlance, a monster.&lt;/p&gt;

&lt;p&gt;Anyway, Shesheshen, our monster, is nursed back to health in human form by Homily. At first, Shesheshen just wants to lay eggs in Homily, but eventually understands her feelings better and tries to find a plan where Homily also lives. Then we get kind of a cock-eyed enemies to lovers romance against the backdrop of some fantasy royal shenanigans with a curse that may or may not involve our monster. It’s all semi-satirical and you’ll probably like it unless the, like, eating people part bugs you. I think I liked what other people loved, but I still did like it quite a bit.&lt;/p&gt;

&lt;h3 id=&quot;21-what-feasts-at-night-by-t-kingfisher&quot;&gt;21: &lt;a href=&quot;https://amzn.to/44hCUg9&quot;&gt;What Feasts At Night&lt;/a&gt; by T. Kingfisher&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Award For&lt;/strong&gt; 2025 Hugo Nominee for Best Novella&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do The Math&lt;/strong&gt; The mathematical average of, say, Edgar Alan Poe, and Bujold’s &lt;em&gt;Brothers In Arms&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I’ve said this before, but Ursula Vernon (who writes adult fiction as T. Kingfisher) is a pretty good comp as this generations Bujold, if Bujold also wrote fractured fairy tales and adorable YA fantasy.&lt;/p&gt;

&lt;p&gt;This book is the second novella in a series that is kind of “what if Miles Vorkosigan was a non-binary soldier for a fictional European country in the 1890sish and went around getting caught by weird supernatural stuff”. It’s creepy, but not too creepy, the world building of the fictional country is quite good, and the main character has good problem-solver vibes.&lt;/p&gt;

&lt;h3 id=&quot;20-bride-by-ali-hazelwood&quot;&gt;20: &lt;a href=&quot;https://amzn.to/3GAARda&quot;&gt;Bride&lt;/a&gt; by Ali Hazelwood&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Do The Math&lt;/strong&gt; I mean, it’s &lt;em&gt;Twilight&lt;/em&gt; + pick your favorite Ali Hazelwood novel + fanfic&lt;/p&gt;

&lt;p&gt;So I saw that Ali Hazelwood put out a paranormal romance between a werewolf and a vampire – pardon me, a vampyre, and that it’s at least partially based on an erotic fanfic universe, and I thought “maybe I can sit this one out”.&lt;/p&gt;

&lt;p&gt;Then I started seeing very positive reviews, and I thought, “well, Libby is free”.&lt;/p&gt;

&lt;p&gt;I’m glad I did, I enjoyed this one, assuming you are willing to accept whatever weird background politics that set up humans vs vampires – pardon me, vampyrs – vs werewolves as different… countries? The selling point here is the voice of the female main character and just a generally well-written bantery romance that uses the paranormal abilities of the characters but also makes the characters interesting in their own right.&lt;/p&gt;

&lt;p&gt;This isn’t exactly a full change from the Ali Hazelwood pattern of “she’s quirky, smart, and sassy, he’s broody and uses a lack of emotional availability to hide how attracted he is to her”, but it’s definitely a way to put it in a different context, and I wound up liking it quite a bit.&lt;/p&gt;

&lt;h2 id=&quot;group-two&quot;&gt;Group Two&lt;/h2&gt;

&lt;p&gt;These are more or less a 4.5 on my scale, which again, means nothing.&lt;/p&gt;

&lt;h3 id=&quot;19-tidal-creatures-by-seanan-mcguire&quot;&gt;19: &lt;a href=&quot;https://amzn.to/3YsCEHA&quot;&gt;Tidal Creatures&lt;/a&gt; by Seanan McGuire&lt;/h3&gt;

&lt;p&gt;Here’s the thing about McGuire. I think I’ve read all her novels, and if you look at her Wikipedia page, you know that’s not a small feat. And I really do like her work, even if I have become quite familiar with the, um, consistent patterns within.&lt;/p&gt;

&lt;p&gt;Because, when she lines things up just right, as she has here, and as she has done very often the Wayward Children series, the results are great.&lt;/p&gt;

&lt;p&gt;This is book three of the Alchemical Journey series, it’s about, I guess Air, and in particular about various aspects of various moon gods and goddesses being killed. Like a lot of McGuire’s books, this involves a number of characters who are constrained to behave a certain way because of certain rules. In this case, the interplay of the various moon gods really did work for me, there was a mystery, there were stakes, there was maybe less pure exposition than the previous book in the series.&lt;/p&gt;

&lt;h3 id=&quot;18-margos-got-money-troubles-by-rufi-thorpe&quot;&gt;18: &lt;a href=&quot;https://amzn.to/3YpBNaA&quot;&gt;Margo’s Got Money Troubles&lt;/a&gt; by Rufi Thorpe&lt;/h3&gt;

&lt;p&gt;This is, I think the highest rated novel on this list that isn’t fantasy/sf/romance/mystery – which is as much about my tastes, I think, as anything else.&lt;/p&gt;

&lt;p&gt;Margo of the title is 20 with a baby after a very ill-advised affair with a professor, and, as the title says, she’s got money troubles. She decides to start an OnlyFans account and with the help of her ex-wrestler dad, creates an online character and a compelling narrative, turns out she’s a natural at it. Shenanigans ensue.&lt;/p&gt;

&lt;p&gt;There’s a version of this story that is darker, and either a more pointed satire, or just a darker view of the world Margo winds up in. The book as written has more of a “woman gets her act together, finds her passion and finds success” vibe, and honestly, I think it works better for that – the satire’s still there, but the book is more upbeat, and I think that tone is more enjoyable to read.&lt;/p&gt;

&lt;h3 id=&quot;17-a-study-in-drowning-by-ava-reid&quot;&gt;17: &lt;a href=&quot;https://amzn.to/433ItfT&quot;&gt;A Study In Drowning&lt;/a&gt; by Ava Reid&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Do The Math&lt;/strong&gt; Divine Rivals - The Front Page + Wuthering Heights?&lt;/p&gt;

&lt;p&gt;At the time I read this, I think it was my favorite of the set of books that I picked up off BookTok recommendations. (It’s been surpassed in 2025, and you’ll just have to wait to find out. Fine. It was &lt;em&gt;Will of the Many&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;This is a decent double feature with &lt;em&gt;Divine Rivals&lt;/em&gt; – it’s a similar WWI-ish tech level, and they both have very capable women as main characters who are fighting uphill battles due to sexism. In this book, our heroine travels to a remote I guess you’d call it a manor to research a book that is basically the country’s national epic novel. The book and its author are both shrouded in mystery, and our lead character has to content with another researcher who is determined to prove the author a fraud.&lt;/p&gt;

&lt;p&gt;So, we get a nice rivals to lovers arc, and the manor setting is depicted with very high levels of brooding atmosphere. The book has atmosphere so think you can almost see it. And… I’m going to have to refresh my memory before the sequel comes out, aren’t I.&lt;/p&gt;

&lt;h3 id=&quot;16-the-adventures-of-amina-al-sirafi-by-s-a-chakraborty&quot;&gt;16: &lt;a href=&quot;https://amzn.to/4d1yAE9&quot;&gt;The Adventures of Amina Al-Sirafi&lt;/a&gt; by S. A. Chakraborty&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Award For:&lt;/strong&gt; Hugo award nominee 2024&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do the Math&lt;/strong&gt; Sinbad the Sailor + representation and a more coherent magic system&lt;/p&gt;

&lt;p&gt;This is just purely delightful, it could maybe be higher, but there’s a lot of good stuff on this list.&lt;/p&gt;

&lt;p&gt;Our title character is a retired pirate, a legend, but now she’s trying to raise a daughter. She’s offered the opportunity (well, offered a deal you can’t refuse…) to get the band back together to help save the daughter of a former crewmate. The beats here are a little familiar, there’s the one by one meeting with the old gang to convince them to do One Last Job. There’s the job itself, which of course isn’t as simple as it appears and eventually there’s magic and chaos and demonic exes and damsels maybe not really in distress.&lt;/p&gt;

&lt;p&gt;It’s got great set pieces and charming characters, it moves like crazy.&lt;/p&gt;

&lt;h3 id=&quot;15-cahokia-jazz-by-francis-spufford&quot;&gt;15: &lt;a href=&quot;https://amzn.to/4jDKzKn&quot;&gt;Cahokia Jazz&lt;/a&gt; by Francis Spufford&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Award For&lt;/strong&gt; Sidewise Award for Alternate History Long Form&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do The Math&lt;/strong&gt; Yiddish Policeman’s Union - 80 years + thousands of miles west and south&lt;/p&gt;

&lt;p&gt;It’s 1922. Sometime in the distant past, a smallpox epidemic was much less deadly than in our timeline, and as a result the US has a midwestern state of Cahokia, with its own Native American cultural and legal traditions and a stronger commitment to multiculturalism than most of the US in 1922.&lt;/p&gt;

&lt;p&gt;As with so many alternate histories, a murder mystery is a great way to have somebody investigate the town and ramp up local tensions. This one explicitly uses noir detective tropes. Our cop hero is down on his luck, would rather be playing piano, and is an outsider even in Cahokia. The investigation turns out to uncover a plan to remove the Native traditions and government of Cahokia.&lt;/p&gt;

&lt;p&gt;I really liked this one. An alternate history that I’d want to visit, a good mystery/thriller, and well-written.&lt;/p&gt;

&lt;h3 id=&quot;14-the-imposition-of-unnecessary-obstacles-by-malka-older&quot;&gt;14: &lt;a href=&quot;https://amzn.to/3GQzMhn&quot;&gt;The Imposition of Unnecessary Obstacles&lt;/a&gt; by Malka Older&lt;/h3&gt;

&lt;p&gt;I love the titles in this series. I love the sentence that the title comes from:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I looked again at the bare platform, empty of amenities, sparse of society, precarious in every way, and wondered again at our human tendency to romanticize the imposition of unnecessary obstacles into our lives.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One of the ways that I can tell that I’m locked into the writing of a book as writing is that I feel the strong tendency to repeat the words of the book aloud. I just did it right now, copying that sentence. I’m not normally a “look at those beautiful words” guy, but… look at those beautiful words, the rhythm, the image, the repeated soft sounds of “empty” “amentities”, “sparse”, “society”, followed by the hard sounds of “precarious”. It’s just gorgeous.&lt;/p&gt;

&lt;p&gt;This book is 200 pages of sentences like that, about two wonderful characters trying to solve a mystery in an amazing setting – Jupiter, colonized by humans in habitats that orbit the planet and are ongoing works in progress. It’s amazing, I want Older to write these stories forever.&lt;/p&gt;

&lt;h3 id=&quot;13-in-universes-by-emet-north&quot;&gt;13: &lt;a href=&quot;https://amzn.to/4iQomYy&quot;&gt;In Universes&lt;/a&gt; by Emet North&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Do The Math:&lt;/strong&gt; It’s got very strong Everything Everywhere All At Once Energy&lt;/p&gt;

&lt;p&gt;In some ways, this was prep for a later, even odder structure of a later book, but that shouldn’t take away from what an accomplishment this book is.&lt;/p&gt;

&lt;p&gt;This isn’t a single story as such, each chapter is the same characters, more or less, in a different universe. There’s usually a tie between adjacent chapters – the first chapter ends with the main character wishing that her crush cared about her as much as she cared, and in the next chapter they’ve been friends since they were kids.&lt;/p&gt;

&lt;p&gt;Eventually, a story emerges about… well, it’s kind of vibes-based, but I read it as being about making choices, about being yourself, about regret. It’s quite unique, and very well done.&lt;/p&gt;

&lt;h3 id=&quot;12-keeping-the-faith-god-democracy-and-the-trial-that-riveted-a-nation-by-brenda-wineapple&quot;&gt;12: &lt;a href=&quot;https://amzn.to/4lVs71H&quot;&gt;Keeping The Faith: God, Democracy, and the Trial That Riveted a Nation&lt;/a&gt; by Brenda Wineapple&lt;/h3&gt;

&lt;p&gt;I think this is our high non-fiction of the year, the true story of the Scopes Monkey Trial. As a longtime fan of play and movie &lt;em&gt;Inherit The Wind&lt;/em&gt; (one of the few pop culture things that my father insisted I watch..), I learned a lot here.&lt;/p&gt;

&lt;p&gt;The complexities of the actual case are obviously greater than you can do in a movie, but I was also struck at how much of the play/movie script was taken from court transcripts. The story is great, an always relevant story about fear of knowledge and how complex that fear becomes politically, plus the compelling stories of the flawed lawyers – Bryan and Darrow (I always have to be careful not to type Brady and Drummond, the Inherit the Wind names). I knew the trial was set up as a test case – Scopes may not have ever actually taught evolution in class. One thing I decidedly did not know, was how deep Bryan was with the KKK, and I also didn’t know how annoyed a lot of the ACLU people were with Darrow.&lt;/p&gt;

&lt;h3 id=&quot;11-long-live-evil-by-sarah-rees-brennan&quot;&gt;11: &lt;a href=&quot;https://amzn.to/4m0Z8JH&quot;&gt;Long Live Evil&lt;/a&gt; by Sarah Rees Brennan&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Award&lt;/strong&gt;: I’m not noting every book that made an NY Times list, but this book was both an NYT 2024 Best of SF/Fantasy and Best of SF Romance&lt;/p&gt;

&lt;p&gt;I came into this one with very high hopes – Brennan is the author of &lt;em&gt;In Other Lands&lt;/em&gt;, which is maybe my favorite portal fantasy of all time.&lt;/p&gt;

&lt;p&gt;Anyway this is probably the best of all the villain themed fantasy novels I read this year. In this one, our heroine is dying of cancer when she is dropped into the world of a series of fantasy novels she and her sister read together. She’s, of course, dropped into character as a minor villain of the piece, and immediately sets about upending the entire works.&lt;/p&gt;

&lt;p&gt;Three quibbles: 1) it takes a while for all the ducks to line up and for the book to get going, 2) Brennan fiddles with the heroine’s specific knowledge of the underlying books in a way that is a little contrived, 3) the book doesn’t end on a cliffhanger exactly, but it does end on a major revelation.&lt;/p&gt;

&lt;p&gt;All that said, this book is great – all of these villain viewpoints hit the line between “fantasy novel” and “parody of fantasy novel” but I think this one is the sharpest parody and the most well worked out fantasy world. It’s also the one where the main character’s personal life before the story begins plays the most impactful role, mostly, but not only, because she goes from sick to very healthy. (I do think calling it a “romance” is a stretch, at most it’s a parody of some romance tropes.). Book two just got pushed out to Feb 2026, though.&lt;/p&gt;

&lt;h3 id=&quot;the-twyford-code-by-janice-hallett&quot;&gt;&lt;a href=&quot;https://amzn.to/42ViIhF&quot;&gt;The Twyford Code&lt;/a&gt; by Janice Hallett&lt;/h3&gt;
&lt;h3 id=&quot;the-appeal-by-janice-hallett&quot;&gt;&lt;a href=&quot;https://amzn.to/4iIpEob&quot;&gt;The Appeal&lt;/a&gt; by Janice Hallett&lt;/h3&gt;
&lt;h3 id=&quot;10-the-mysterious-case-of-the-alperton-angels-by-janice-hallett&quot;&gt;10: &lt;a href=&quot;https://amzn.to/4k5Iua8&quot;&gt;The Mysterious Case of the Alperton Angels&lt;/a&gt; by Janice Hallett&lt;/h3&gt;

&lt;p&gt;I went on a big Hallett kick at the end of the year, read all her extant novels in about three weeks. All of them are mystery novels based on what purports to be found material. In the case of &lt;em&gt;The Appeal&lt;/em&gt;, it’s emails and texts among a community theater group in the months leading up to a murder. &lt;em&gt;Twyford Code&lt;/em&gt; has a series of transcripts of voice messages left on a phone. In the case of &lt;em&gt;Alperton Angels&lt;/em&gt;, you have access to all the notes gathered by a true crime writer investigating a cold case.&lt;/p&gt;

&lt;p&gt;There’s no detective character, exactly (in some of the books you are perusing the materials alongside characters who occasionally comment), so the plot is completely manufactured by the order in which the materials are presented. (This is true of every mystery, but Hallett is doing the bare-brick exposed beams version of a mystery novel.)&lt;/p&gt;

&lt;p&gt;I found all of these books very compelling – they just clicked with some puzzle-loving part of my brain. I also spent an inordinate amount of time thinking about what this format gains you and loses you versus a normal mystery novel. Hint: you can do very, very fun things with hiding information in plain sight – each of these books has some variation on a character that isn’t quite what they seem.&lt;/p&gt;

&lt;p&gt;I recommend all of these – I liked &lt;em&gt;Alperton Angels&lt;/em&gt; the most because I think the underlying plot is the most satisfying and also because the framing device has the most urgency behind it, but I liked them all.&lt;/p&gt;

&lt;h3 id=&quot;9-a-sorceress-comes-to-call-by-t-kingfisher&quot;&gt;9: &lt;a href=&quot;https://amzn.to/43c6u5E&quot;&gt;A Sorceress Comes To Call&lt;/a&gt; by T. Kingfisher&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Award&lt;/strong&gt; 2025 Hugo Nominee for Best Novel
&lt;strong&gt;Award&lt;/strong&gt; 2025 Nebula Nominee for Best Novel&lt;/p&gt;

&lt;p&gt;I don’t have a lot to say about this, actually. Or at least not a lot that I haven’t said about the seventy-umpteen other Kingfisher books I’ve written about in the last few years. This is Kingfisher in “dark fairytale” mode, the book is nominally a retelling of “The Goose Girl” and I guess that is true inasmuch as it contains a goose. Pretty much everything else is Kingfisher.&lt;/p&gt;

&lt;p&gt;Our lead character is a girl who is literally under the thumb of her witch mother – definite trigger warning for abusive behavior here – where the mother can literally force her to do things, essentially magically controlling her body. The mother has plans but they eventually come into the orbit of some, like, normal people.&lt;/p&gt;

&lt;p&gt;This is darker than some of Kingfisher’s other fairy tale stuff, but the dialog and voice are great and the ending is satisfying.&lt;/p&gt;

&lt;h3 id=&quot;8-wicked-problems-by-max-gladstone&quot;&gt;8: &lt;a href=&quot;https://amzn.to/3Z1QErL&quot;&gt;Wicked Problems&lt;/a&gt; by Max Gladstone&lt;/h3&gt;

&lt;p&gt;This is, I think, book eight in the Craft Sequence, and as such is probably not the greatest starting-off point. There’s a complicated triangle, where a dangerous force threatens the world, and two separate groups are trying to stop it, and also stop each other – there’s a lot to sum up. What Gladstone does very well is create a world that feels both modern and uncanny, with technology and magic and laws and gods and all kinds of things mixed together into a very distinct tone.&lt;/p&gt;

&lt;h3 id=&quot;7-shark-heart-a-love-story-by-emily-habeck&quot;&gt;7: &lt;a href=&quot;https://amzn.to/3EN3FPg&quot;&gt;Shark Heart: A Love Story&lt;/a&gt; by Emily Habeck&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; Just your typical boy gets girl, boy turns into shark, boy loses girl story&lt;/p&gt;

&lt;p&gt;Here’s the plot: Lewis marries Wren and then is diagnosed with a condition that will cause him to turn into a shark. Lewis then turns into a shark.&lt;/p&gt;

&lt;p&gt;This book is called magic realism or fantasy a lot, but, and I don’t really want to make a big deal out this, it’s science fiction. Really. Lewis is treated by doctors, there are medical procedures, his condition is not considered magical in-universe. Even though nobody else, even possibly the author, thinks this is science-fiction, by my definition, it clearly is.&lt;/p&gt;

&lt;p&gt;Like the best science fiction, Lewis’ plight is both a metaphor and not a metaphor. It’s about dealing with terminal illness in the abstract, but it is also a concrete thing in the world that causes specific, non-metaphorical changes that he has to deal with, like growing gills or rough skin.&lt;/p&gt;

&lt;p&gt;The book is unusual structurally, a series of short vignettes, some of which are written as screenplays, and doesn’t really have, like a smooth plot. But the story is just devastatingly beautiful.&lt;/p&gt;

&lt;h2 id=&quot;group-one--these-were-all-475-or-5&quot;&gt;Group One – These were all 4.75 or 5&lt;/h2&gt;

&lt;h3 id=&quot;6-the-teller-of-small-fortunes-by-julie-leong&quot;&gt;6: &lt;a href=&quot;https://amzn.to/4iHbGmB&quot;&gt;The Teller of Small Fortunes&lt;/a&gt; by Julie Leong&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Do The Math:&lt;/strong&gt; Monk and Robot, but in a fantasy world, and with more plot&lt;/p&gt;

&lt;p&gt;Another one that might not be for everybody (I’ve seen reviews that felt it was slow), this is basically a found family story. Our hero is a Teller of Small Fortunes, she travels from city to city to tell people mundane details about their immediate futures. She actually can tell larger futures, but that has lead to sadness in her past and now she avoids it.&lt;/p&gt;

&lt;p&gt;This is a pretty standard, well-executed, found family story. The kind where our main character sees herself as alone and unloved, possibly even unlovable, but picks up a cohort of other like-minded people who perhaps have their own issues. There’s a quest – in this case to find the missing child of one of her new companions. There are family issues, and there’s a larger political context that plays a role, it’s clearly meant to mirror our world in some ways, but is still very much its own thing.&lt;/p&gt;

&lt;h3 id=&quot;5-warp-your-own-way-by-ryan-north-and-chris-fenoglio&quot;&gt;5: &lt;a href=&quot;https://amzn.to/43c6RNA&quot;&gt;Warp Your Own Way&lt;/a&gt; by Ryan North and Chris Fenoglio&lt;/h3&gt;

&lt;p&gt;I read a lot of comics, they don’t always make it on this list for various reasons. Ryan North is easily my favorite comic writer of the last decade. I loved his Squirrel Girl run, he’s currently writing Fantastic Four, and it’s just wonderful – the best possible mix of modern characters and silver-age science weirdness.&lt;/p&gt;

&lt;p&gt;And he writes this Star Trek: Lower Decks comic, another set of characters that I have come to love.&lt;/p&gt;

&lt;p&gt;This is a graphic Lower Decks novel. It’s actually a graphic Choose Your Own Adventure Lower Decks novel. And it’s brilliant.&lt;/p&gt;

&lt;p&gt;So, the first amazing thing North does here is that the story is basically a time-loop story. So doing it over and over again is literally the point of the story. Second, through some various means that I won’t spoil, North more or less guarantees that you will have a particular experience with the book – you have to go through the paths a few times before you can move forward.&lt;/p&gt;

&lt;p&gt;He has somehow crafted a CYOA story that has full branchability and yet the entire meta experience of doing the story over and over again has a satisfying arc. And it’s true to the Lower Decks characters (mostly Mariner), and it’s funny. Just an amazing piece of work.&lt;/p&gt;

&lt;h3 id=&quot;4-bury-your-gays-by-chuck-tingle&quot;&gt;4: &lt;a href=&quot;https://amzn.to/4d7mdGM&quot;&gt;Bury Your Gays&lt;/a&gt; by Chuck Tingle&lt;/h3&gt;

&lt;p&gt;I did not expect to be in a world where Chuck Tingle was the author of a top-ten book two years in a row, but here we are. In this one, our hero Misha is a TV writer who is about to bring together the show’s two leads in a same-sex ship that the fans have long pushed for. Instead, he’s told to kill the characters off, because the studio, via an algorithm, says it will be better for ratings. When Misha refuses, he finds himself stalked by horror movie characters from his past.&lt;/p&gt;

&lt;p&gt;This book is both genuinely creepy and also quite funny (granted, it’s a dark, satiric kind of funny). It works both in its character bits, and I also found the plot resolution very satisfying (though milage may vary on that point, you might find it too clever by half). Horror is not usually my thing, but pop-culture meta-commentary horror, apparently I can handle that.&lt;/p&gt;

&lt;h3 id=&quot;3-the-mars-house-by-natasha-pulley&quot;&gt;3: &lt;a href=&quot;https://amzn.to/4k5L5Rq&quot;&gt;The Mars House&lt;/a&gt; by Natasha Pulley&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; What if Kim Stanley Robinson wrote Romantacy?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do The Math:&lt;/strong&gt; It’s the mathematical average of Red Mars and Winter’s Orbit&lt;/p&gt;

&lt;p&gt;I’m honestly a little surprised this didn’t get more awards recognition. Our setup here is on Mars, basically post Earth’s environmental collapse. Earth-born settlers on Mars are regulated, though, because their greater mass and strength makes them dangerous to those raised under Mars gravity, in particular, they are often required to wear exoskeletons that limit their strength.&lt;/p&gt;

&lt;p&gt;This book is blurbed as being “perfect for readers of Sarah Gailey and Tamsyn Muir”, which, I have to be honest, I have no idea what that means, except as a signal that the book is not going to be heteronormative.&lt;/p&gt;

&lt;p&gt;Our main characters are January, a climate refugee new to Mars, and Aubrey, a politician who is the face of the “let’s regulate those Earthling maniacs” movement. (Aubrey is, if I remember correctly, never explicitly gendered in the book, not necessarily by their preference but because high Martian society considers public displays of gender to be gauche.)&lt;/p&gt;

&lt;p&gt;Through shenanigans, January and Aubrey wind up in a political marriage, and we get an enemies-to-lovers story with a large number of unique beats. Pulley has an eye for making her Martian society feel just a little bit weird, so we get communication with animals, and also ubiquitous augmented reality, which has some interesting side effects.&lt;/p&gt;

&lt;p&gt;This is fun – it’s got better SF tropes than a lot of SF Romance (can we get a good portmanteau like “romantasy”?). I think the romance basically works, it’s got a couple of bonkers in the best way scenes.&lt;/p&gt;

&lt;h3 id=&quot;2-the-tainted-cup-by-robert-jackson-bennett&quot;&gt;2: &lt;a href=&quot;https://amzn.to/3ESUqwX&quot;&gt;The Tainted Cup&lt;/a&gt; by Robert Jackson Bennett&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Do The Math:&lt;/strong&gt; Holmes &amp;amp; Watson + Godzilla + Little Shop of Horrors. Let’s go with that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Awards&lt;/strong&gt;: 2025 Hugo nominee for Best Novel&lt;/p&gt;

&lt;p&gt;I was genuinely surprised to see how much love I saw this book get online, I guess Robert Jackson Bennet isn’t my underrated discovery anymore.&lt;/p&gt;

&lt;p&gt;The first sentence of the blurb for this book is “In Daretana’s greatest mansion, a high imperial officer lies dead–killed, to all appearances, when a tree erupted from his body” yep. A tree. And it’s murder. By magical fast-growing tree.&lt;/p&gt;

&lt;p&gt;So there’s an empire. And the empire is beset annually by huge leviathans that rise up out of the sea and create havoc until they are killed. And so, the empire has learned how to use the leviathan blood and native plants to do all kinds of uncanny things. In particular, there are a whole class of augmented people with heightened senses or super strength or whatever.&lt;/p&gt;

&lt;p&gt;And we have Ana, our detective. She’s probably augmented, and she’s so overwhelmed by patterns that she needs to be in sensory controlled spaces much of the time. And we have Din, her assistant. He’s augmented to have a perfect memory, and so he goes out and gathers facts, and she solves the mystery. Like how somebody might die from fast-growing tree.&lt;/p&gt;

&lt;p&gt;The world building here is incredible. We’re at the outskirts of the Empire but Bennet makes it seem palpable, we see the pressures they are under, we see how just flat-out weird the magical augments are, and what they cost the augmented who bear them. The whole world feels squishy and menacing and just disturbing in the best way. And it’s a great mystery. (Book two is also great).&lt;/p&gt;

&lt;h3 id=&quot;1-remember-you-will-die-by-eden-robins&quot;&gt;1: &lt;a href=&quot;https://amzn.to/3ECuYvF&quot;&gt;Remember You Will Die&lt;/a&gt; by Eden Robins&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Elevator Pitch:&lt;/strong&gt; I’m not sure I can beat the official “Sixty Protagonists, All of Whom Are Dead”&lt;/p&gt;

&lt;p&gt;This is maybe my most idiosyncratic top choice ever. It’s not going to get awards love, I don’t see a million people talking about it on BookTok. As I write this, its average rating on StoryGraph is 3.52. Without checking, I’m going to bet that’s very close to the lowest of any book on this list. I may not be able to convince you to read this one.&lt;/p&gt;

&lt;p&gt;And yet.&lt;/p&gt;

&lt;p&gt;I picked up this book because Mary Robinette Kowal raved about it on social media, so maybe I came into it primed.&lt;/p&gt;

&lt;p&gt;We start with a news article. Actually we start with some epigraphs, and they are unusually important, so read them. Anyway, we start with a news article dated 2102 about the death of a girl believed to have been named Poppy. We then get a brief note about the etymology of the word “poppy”. With sample sentences. Don’t overlook the sample sentences, they are important. We then get a seemingly unrelated news article from 1864, and the a further 2102 article that explains that Poppy was the only daughter of an AI construct named Peregrine.&lt;/p&gt;

&lt;p&gt;That’s the rest of the book, we go back and forth across the timeline, mostly obituaries – a person will be mentioned in one obit, and then we’ll see the that character’s obit. The etymology sections are interspersed and so is some other found text. Sometimes what isn’t said is as important as what is – in one case we get multiple obits of the same person with different perspectives on what they can and can’t say. It’s also an alt-history in ways that I’m not going to give away – the book is even weirder than I’m hinting at.&lt;/p&gt;

&lt;p&gt;As the reader, it eventually dawns on you to try and wonder why these articles are being presented in this order. There is an answer – or at least I think there is, it’s not quite spelled out in the book. Puzzling out the answer is part of the deal here. Over the course of the book, we get like 250 years of intertwined characters, and it’s inventive and beautiful and heartbreaking.&lt;/p&gt;

&lt;p&gt;And look, I gasped aloud at one of the sample sentences (it’s in the etymology of “grief”), and I bought the book even though I originally got it through Libby, and I’m choking up a little just writing about it. But this book might not work for you – the rating average suggests it probably won’t. But, wow, did it work for me.&lt;/p&gt;</content><author><name>Noel Rappin</name></author><category term="books" /></entry><entry><title type="html">Better Know A Ruby Thing: Method Lookup</title><link href="https://noelrappin.com/2025/03/better-know-a-ruby-thing-method-lookup/" rel="alternate" type="text/html" title="Better Know A Ruby Thing: Method Lookup" /><published>2025-03-09T00:00:00+00:00</published><updated>2025-03-09T00:00:00+00:00</updated><id>repo://blog.collection/_blog/2025-03-09-better-know-a-ruby-thing-method-lookup.md</id><content type="html" xml:base="https://noelrappin.com/2025/03/better-know-a-ruby-thing-method-lookup/">&lt;h2 id=&quot;notes-and-corrections&quot;&gt;Notes and Corrections&lt;/h2&gt;

&lt;p&gt;Before we get fully started here, a couple of notes on &lt;a href=&quot;https://noelrappin.com/blog/2025/01/better-know-a-ruby-thing-singleton-classes/&quot;&gt;Better Know Singleton Classes&lt;/a&gt;, which, among other things, got mentioned on Hacker News, giving me comments there for the first time in years, maybe for the first time ever.&lt;/p&gt;

&lt;p&gt;One Hacker News comment suggested that “Eigenclass” was coined by _why the lucky stiff as a joke and was then adopted by the community. I looked this up and that doesn’t seem to be the case… _why’s &lt;a href=&quot;http://poignant.guide&quot;&gt;Poignant Guide To Ruby&lt;/a&gt; uses “metaclass” when it discusses this feature (at least the version I have does…). Matz used “eigenclass” in the O’Reilly Ruby book. (You didn’t ask, but version 1 of the Pickaxe uses “singleton class”, per &lt;a href=&quot;https://ruby-doc.com/docs/ProgrammingRuby/&quot;&gt;https://ruby-doc.com/docs/ProgrammingRuby/&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;It’s also worth mentioning that both “eigenclass” and “metaclass” are used in the Ruby source, but to mean slightly different things. I did not stare at the code long enough to figure out exactly how they are different. For example, this is a comment in &lt;code class=&quot;highlighter-rouge&quot;&gt;module.h&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;eigenclass: &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;singleton&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;metaclass: &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Metaclass&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kind&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;singleton&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I think I can get this to make sense if I stare at it hard enough, but I am not at all sure.&lt;/p&gt;

&lt;p&gt;One or two people (including &lt;a href=&quot;https://bsky.app/profile/ufuk.dev&quot;&gt;https://bsky.app/profile/ufuk.dev&lt;/a&gt;) correctly noted that my description of how singleton classes work in Ruby is, at best, simplified, and at worst, inaccurate. Basically, my description is consistent with what Ruby’s API allows you to determine about classes, but is not a reflection of how Ruby works internally. Ruby’s internals are closer to Smalltalk than I let on, we’ll talk more about that in a second, because it’s important to method lookup.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;I haven’t had this inserted commercial message in the last few emails, because I fear that it is annoying, but then we picked up a handful of new readers from the Hacker News thing so… quick tour, bear with me.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;I’m the co-author of Programming Ruby 3.3 (The Pickaxe Book) which is available in &lt;a href=&quot;https://pragprog.com/titles/ruby5/programming-ruby-3-3-5th-edition/&quot;&gt;ebook from Pragmatic Press&lt;/a&gt; and physically from &lt;a href=&quot;https://amzn.to/3Vk4apx&quot;&gt;Amazon&lt;/a&gt; among others. I’d love it if you purchased a copy.&lt;/li&gt;
  &lt;li&gt;If you’d like to support this newsletter, you can sign up for a &lt;a href=&quot;https://buy.stripe.com/5kAg1x11FfY0fyEdQU&quot;&gt;Monthly Subscription for $3/month&lt;/a&gt; or an &lt;a href=&quot;https://buy.stripe.com/28odTpaCf4fibiodQV&quot;&gt;Annual Subscription for $30/year&lt;/a&gt;. Subscription money goes toward paying for Buttondown. Paid subscribers (very) occasionally get extras.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks!&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;method-lookup-in-ruby&quot;&gt;Method Lookup In Ruby&lt;/h2&gt;

&lt;p&gt;And with that, let’s talk about method lookup in Ruby, which is from one perspective really clean and simple, and from another angle, kind of messy.&lt;/p&gt;

&lt;p&gt;This is part of what the &lt;a href=&quot;https://docs.ruby-lang.org/en/master/syntax/calling_methods_rdoc.html&quot;&gt;official Ruby documentation&lt;/a&gt; says about method lookup:&lt;/p&gt;

&lt;div class=&quot;language-markdown highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Here is the order of method lookup
for the receiver&apos;s class or module &lt;span class=&quot;sb&quot;&gt;`R`&lt;/span&gt;:
&lt;span class=&quot;p&quot;&gt;
-&lt;/span&gt; The prepended modules of &lt;span class=&quot;sb&quot;&gt;`R`&lt;/span&gt; in reverse order
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; For a matching method in &lt;span class=&quot;sb&quot;&gt;`R`&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; The included modules of &lt;span class=&quot;sb&quot;&gt;`R`&lt;/span&gt; in reverse order

If &lt;span class=&quot;sb&quot;&gt;`R`&lt;/span&gt; is a class with a superclass,
this is repeated with &lt;span class=&quot;sb&quot;&gt;`R`&lt;/span&gt;&apos;s superclass until a method is found.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What’s striking about this is it is:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;not fully accurate, because it doesn’t include the singleton class&lt;/li&gt;
  &lt;li&gt;overly confusing, because there’s a simpler way to talk about the lookup order.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To be fair, this is how I talked about method lookup in Ruby for a long time. The way I’d describe now it is:&lt;/p&gt;

&lt;p&gt;Given a receiver object, &lt;code class=&quot;highlighter-rouge&quot;&gt;r&lt;/code&gt;,&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Ruby takes the list of classes and modules that you can see defined in &lt;code class=&quot;highlighter-rouge&quot;&gt;r.singleton_class.ancestors&lt;/code&gt;. This list contains all the classes and modules in &lt;code class=&quot;highlighter-rouge&quot;&gt;r&lt;/code&gt;’s ancestor tree.&lt;/li&gt;
  &lt;li&gt;Ruby looks at the first element in the ancestors list and if it has the method, that’s called. If not, it goes to the next element and so on..&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: Ruby does not literally call this &lt;code class=&quot;highlighter-rouge&quot;&gt;ancestors&lt;/code&gt; method – I don’t think – but this is conceptually the list that Ruby uses for lookup.&lt;/p&gt;

&lt;p&gt;For example…&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Pre&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;say&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;prefix&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Kla&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;prepend&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Pre&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;say&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;class&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Kla&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;singleton_class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ancestors&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Results in:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Kla&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x0000000122d165f0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;no&quot;&gt;Pre&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;no&quot;&gt;Kla&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;no&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;no&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Ext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Generator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;GeneratorMethods&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;no&quot;&gt;PP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ObjectMixin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;no&quot;&gt;Kernel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;no&quot;&gt;BasicObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The point here is if I were to call the method &lt;code class=&quot;highlighter-rouge&quot;&gt;r.say&lt;/code&gt;, Ruby will first look in the singleton class, where it will not find a &lt;code class=&quot;highlighter-rouge&quot;&gt;say&lt;/code&gt; method, and then look in the prepended module, &lt;code class=&quot;highlighter-rouge&quot;&gt;Pre&lt;/code&gt;, where it will find the method and it will execute that call. Ruby will then stop, so the fact that the method is also defined in the class &lt;code class=&quot;highlighter-rouge&quot;&gt;Kla&lt;/code&gt; is not used.&lt;/p&gt;

&lt;p&gt;If we then write this code:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;say&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;banana&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then the singleton class does have a &lt;code class=&quot;highlighter-rouge&quot;&gt;say&lt;/code&gt; method and &lt;code class=&quot;highlighter-rouge&quot;&gt;r.say&lt;/code&gt; now returns &lt;code class=&quot;highlighter-rouge&quot;&gt;banana&lt;/code&gt; and stops lookup at that point.&lt;/p&gt;

&lt;h2 id=&quot;so-how-does-one-get-on-the-ancestor-list&quot;&gt;So, how does one get on the ancestor list?&lt;/h2&gt;

&lt;p&gt;The official Ruby documentation for method lookup effectively describes this process of creating the ancestor list.&lt;/p&gt;

&lt;p&gt;Conceptually, the ancestor list looks like this:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Start with the inheritance tree, from the class to its superclass and so on to &lt;code class=&quot;highlighter-rouge&quot;&gt;Object&lt;/code&gt; and then &lt;code class=&quot;highlighter-rouge&quot;&gt;BasicObject&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;For each class in that list, any modules that are added using &lt;code class=&quot;highlighter-rouge&quot;&gt;prepend&lt;/code&gt; are inserted in the ancestor list before the class that prepends them. If the class prepends more then one module, a second prepended class is added before the first prepended class, and so on. The result is that modules that are prepended later supersede modules prepended earlier.&lt;/li&gt;
  &lt;li&gt;For each class in the list, any modules that are added using &lt;code class=&quot;highlighter-rouge&quot;&gt;include&lt;/code&gt; are inserted after the class that includes them. Again, modules included later are inserted earlier, so that the last module included wins.&lt;/li&gt;
  &lt;li&gt;This process is recursive, if an included module also has includes and prepends, those modules also go on the tree relative to that module.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can see these behaviors in our example. The &lt;code class=&quot;highlighter-rouge&quot;&gt;Kla&lt;/code&gt; class prepends the &lt;code class=&quot;highlighter-rouge&quot;&gt;Pre&lt;/code&gt; module, so &lt;code class=&quot;highlighter-rouge&quot;&gt;Pre&lt;/code&gt; is in the ancestor tree before &lt;code class=&quot;highlighter-rouge&quot;&gt;Kla&lt;/code&gt;. Later, &lt;code class=&quot;highlighter-rouge&quot;&gt;JSON::Ext::Generator::GeneratorMethods::Object&lt;/code&gt;,
 &lt;code class=&quot;highlighter-rouge&quot;&gt;PP::ObjectMixin&lt;/code&gt;, and &lt;code class=&quot;highlighter-rouge&quot;&gt;Kernel&lt;/code&gt; are all included by &lt;code class=&quot;highlighter-rouge&quot;&gt;Object&lt;/code&gt;. (Technically, the JSON and PP modules are, I think, monkey patched in when the &lt;code class=&quot;highlighter-rouge&quot;&gt;JSON&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;PP&lt;/code&gt; gems are auto-included, &lt;code class=&quot;highlighter-rouge&quot;&gt;Kernel&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;Object&lt;/code&gt; also are a little non-standard in their actual internals, for performance reasons).&lt;/p&gt;

&lt;h2 id=&quot;but-i-thought-we-said-something-about-singleton-classes&quot;&gt;But I thought we said something about singleton classes?&lt;/h2&gt;

&lt;p&gt;We did. Or rather, I did.&lt;/p&gt;

&lt;p&gt;What I said in the past blog post, and actually, the way I was taught Ruby, was that Ruby objects effectively have a pointer to their singleton class, and that method lookup is sort of special-cased to start at the singleton class.&lt;/p&gt;

&lt;p&gt;That’s, like, true-ish, in the sense that following that model will allow you to accurately predict Ruby’s behavior, but it’s not how the internals work. Even what I said above about method lookup being based on the ancestor list of &lt;code class=&quot;highlighter-rouge&quot;&gt;obj.singleton_class&lt;/code&gt; isn’t exactly how the internals work, though it’s (I think) as close as you can get given Ruby’s public API.&lt;/p&gt;

&lt;p&gt;What Ruby actually does, and thanks to &lt;a href=&quot;https://bsky.app/profile/ufuk.dev/post/3lgpxplm4lk2u&quot;&gt;Ufuk Kayserilioglu&lt;/a&gt; for this, is that Ruby makes the singleton class the “Class” of the instance, and then makes the actual class the superclass of the singleton class. Ruby hides this from you in the public library methods – if you say &lt;code class=&quot;highlighter-rouge&quot;&gt;&quot;thing&quot;.class&lt;/code&gt;, you get &lt;code class=&quot;highlighter-rouge&quot;&gt;String&lt;/code&gt;, not thing’s singleton class. I think – I think – that the only way you can really see the actual inheritance tree is by asking the singleton class for its ancestors list.&lt;/p&gt;

&lt;p&gt;Why does it matter how singleton classes work? Well, integrating singleton classes into the ancestor list makes method look up much more elegant. You don’t have to do weird contortions to talk about included and prepended modules the way the official docs do. It’s just a simple walk up the ancestor list, all the extra modules and singleton classes are rolled up into that list and all you need to do is go through the list one by one. The complication is in creating that ancestor list, not in traversing it.&lt;/p&gt;

&lt;h2 id=&quot;what-if-the-method-isnt-found&quot;&gt;What if the method isn’t found?&lt;/h2&gt;

&lt;p&gt;That’s usually bad.&lt;/p&gt;

&lt;p&gt;If you get all the way up to &lt;code class=&quot;highlighter-rouge&quot;&gt;BasicObject&lt;/code&gt; and nobody has matched the method name, then the method is… wait for it… missing. I did a &lt;a href=&quot;https://noelrappin.com/blog/2023/10/better-know-a-ruby-thing-10-method_missing/&quot;&gt;whole newsletter about method_missing&lt;/a&gt;, so we’re not going to go over that whole thing.&lt;/p&gt;

&lt;p&gt;The point here is how Ruby behaves, and what Ruby does is start the search all over again from the singleton class of the original receiver object, but this time rather than searching for the method name, it searches for &lt;code class=&quot;highlighter-rouge&quot;&gt;method_missing&lt;/code&gt;. If some class or module in the chain implements &lt;code class=&quot;highlighter-rouge&quot;&gt;method_missing&lt;/code&gt;, it’s executed. Eventually you get to &lt;code class=&quot;highlighter-rouge&quot;&gt;BasicObject&lt;/code&gt;, which definitely implements &lt;code class=&quot;highlighter-rouge&quot;&gt;method_missing&lt;/code&gt; – specifically it throws a &lt;code class=&quot;highlighter-rouge&quot;&gt;NameError&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;thats-super&quot;&gt;That’s &lt;code class=&quot;highlighter-rouge&quot;&gt;super&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;Most Object-Oriented languages let you execute a superclass method with the keyword &lt;code class=&quot;highlighter-rouge&quot;&gt;super&lt;/code&gt; or something similar, and Ruby is not an exception. Ruby has keywords for when the parser needs to do something unusual, and &lt;code class=&quot;highlighter-rouge&quot;&gt;super&lt;/code&gt; looks like a method call but doesn’t quite behave like a method call.&lt;/p&gt;

&lt;p&gt;A call to &lt;code class=&quot;highlighter-rouge&quot;&gt;super&lt;/code&gt; is evaluated as “take the name of the method the super call is in, call that method, but start one level up on the &lt;code class=&quot;highlighter-rouge&quot;&gt;ancestors&lt;/code&gt; list”. In other words, &lt;code class=&quot;highlighter-rouge&quot;&gt;super&lt;/code&gt; doesn’t only follow the inheritance hierarchy, it follows the full &lt;code class=&quot;highlighter-rouge&quot;&gt;ancestors&lt;/code&gt; list with modules that have been added using &lt;code class=&quot;highlighter-rouge&quot;&gt;include&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;prepend&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There are a couple of fun side-effects of that definition.&lt;/p&gt;

&lt;p&gt;Calling &lt;code class=&quot;highlighter-rouge&quot;&gt;super&lt;/code&gt; from a prepended module will almost always take you back to the original method. This means you can create wrappers to insert when debugging…&lt;/p&gt;

&lt;p&gt;If you want to trace an unreachable method in a class you don’t own – Fill in your own name instead of &lt;code class=&quot;highlighter-rouge&quot;&gt;unreachable_method&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Debug&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;unreachable_method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;calling unreachable method with &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;caller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ClassIDontOwn&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;prepend&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Debug&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I’m prepending a module to capture calls, but then using &lt;code class=&quot;highlighter-rouge&quot;&gt;super&lt;/code&gt; to continue along the normal track. Calling &lt;code class=&quot;highlighter-rouge&quot;&gt;super&lt;/code&gt; is a technique to use with &lt;code class=&quot;highlighter-rouge&quot;&gt;method_missing&lt;/code&gt;, as well – handle the method name if you can, continue normal processing with &lt;code class=&quot;highlighter-rouge&quot;&gt;super&lt;/code&gt;. (Before &lt;code class=&quot;highlighter-rouge&quot;&gt;prepend&lt;/code&gt; you could do something similar by aliasing a method, but that was more complex and confusing).&lt;/p&gt;

&lt;p&gt;The way that &lt;code class=&quot;highlighter-rouge&quot;&gt;super&lt;/code&gt; handles arguments is what makes it a keyword.&lt;/p&gt;

&lt;p&gt;Invoking &lt;code class=&quot;highlighter-rouge&quot;&gt;super&lt;/code&gt; with no arguments continues the call stack implicitly passing the same arguments that were passed to the original method. But, you can override this – if you explicitly pass arguments to &lt;code class=&quot;highlighter-rouge&quot;&gt;super&lt;/code&gt;, then those arguments are what’s passed to the next method in the list. This is a useful feature in the not-unheard-of case where a subclass adds arguments to &lt;code class=&quot;highlighter-rouge&quot;&gt;initialize&lt;/code&gt; that are not in the parent class:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Parent&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Child&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Parent&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@age&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;age&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In this case, the &lt;code class=&quot;highlighter-rouge&quot;&gt;Child&lt;/code&gt; class takes an extra argument, but passes just the first argument back to the &lt;code class=&quot;highlighter-rouge&quot;&gt;Parent&lt;/code&gt; class to get its functionality.&lt;/p&gt;

&lt;p&gt;Which gets us to a legitimate piece of trivia, the only (I think) place in Ruby where empty parentheses are syntacticly meaningful is in a call to &lt;code class=&quot;highlighter-rouge&quot;&gt;super&lt;/code&gt;. If you want to explicitly pass no arguments to the parent class, the call needs to be &lt;code class=&quot;highlighter-rouge&quot;&gt;super()&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Thing&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;value&lt;/span&gt;
    &lt;span class=&quot;mi&quot;&gt;25&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LittleThing&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Thing&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;expensive&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;expensive&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The parent class method takes no arguments so the subclass needs to explicitly pass no arguments to the parent in order to use &lt;code class=&quot;highlighter-rouge&quot;&gt;super&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;did-you-know-refinements-are-a-thing&quot;&gt;Did you know refinements are a thing?&lt;/h2&gt;

&lt;p&gt;Yes, they are. Refinements are a way of customizing Ruby’s behavior locally – it’s a monkey patch but it’s only applicable where explicitly mixed in. Refinements change class method lookup, but not for the class that incudes the refinement, but for the class referenced by the refinement. Without going into a 1500 word digression for a feature I’ve used exactly once since it was added to Ruby ten years ago…&lt;/p&gt;

&lt;p&gt;If a refinement is active for the receiving class or module, then the refinement is effectively placed in the ancestor list before the class or module and before anything prepended by the class or module. I don’t really know how this works internally and I have to save something for Future Noel to do if we ever decide to Better Know refinements, but effectively anything in the refinement goes after the singleton class and before anything that is actually connected to the class. If, for some reason, the refinement prepends or includes modules, those are placed relative to the refinement exactly as they would be for a class or module.&lt;/p&gt;

&lt;h2 id=&quot;hey-i-was-wondering-can-a-singleton-class-have-a-singleton-class&quot;&gt;Hey, I was wondering… can a singleton class have a singleton class&lt;/h2&gt;

&lt;p&gt;Yes. We will not be investigating this further.&lt;/p&gt;</content><author><name>Noel Rappin</name></author><category term="ruby" /><category term="better_know" /><category term="dynamic_rubyist" /><category term="singleton" /></entry><entry><title type="html">Better Know A Ruby Thing: Singleton Classes</title><link href="https://noelrappin.com/2025/01/better-know-a-ruby-thing-singleton-classes/" rel="alternate" type="text/html" title="Better Know A Ruby Thing: Singleton Classes" /><published>2025-01-20T00:00:00+00:00</published><updated>2025-01-20T00:00:00+00:00</updated><id>repo://blog.collection/_blog/2025-01-20-better-know-a-ruby-thing-singleton-classes.md</id><content type="html" xml:base="https://noelrappin.com/2025/01/better-know-a-ruby-thing-singleton-classes/">&lt;p&gt;It is time to Better Know what is perhaps the Ruby-iest of Ruby things, a feature that didn’t even have an official name for several years, despite being critical to Ruby’s Object-Oriented semantics. (It only just now occurs to me that there was no official name &lt;em&gt;in English&lt;/em&gt;, I wonder what the Japanese name for it was…).&lt;/p&gt;

&lt;p&gt;Yes, it’s the singleton class.&lt;/p&gt;

&lt;p&gt;Which isn’t really a singleton. Or really a class. It is the “grape-nuts cereal” of Ruby features.&lt;/p&gt;

&lt;p&gt;The singleton class has been known by other unofficial names over the years. It’s been called a “metaclass” although technically it is not a metaclass, it has been called an “eigenclass”, a name I always favored because nobody knows what an eigenclass is, so whose to say whether it is one.&lt;/p&gt;

&lt;p&gt;I went down a small rabbit hole on “eigenclass”. As far as I can tell, the term “eigenclass” is unique to Ruby, but I didn’t look that hard, maybe Matz picked it up from some obscure 60s language. I think it’s analogous to the math term “eigenvalue”, which Wikipedia says is a term in linear algebra. I may have known what that meant at some point, but I sure don’t know it any more. “Eigen” is a German word that is a cognate with the English word “own” (thanks Wikipedia) but not the sense of “own” that means “has possession of” – the sense that means “very own”, or “is characteristic of”, as in “my very own way of thinking” or “Chicago’s very own hot dog”. So an “eigenclass” is somehow a characteristic class.&lt;/p&gt;

&lt;p&gt;Anyway, the lack of naming convention was super confusing, especially if you were trying to teach Ruby. Ruby 2.1 introduced a accessor method named &lt;code class=&quot;highlighter-rouge&quot;&gt;singleton_method&lt;/code&gt;, and I think that was what settled the naming convention, to a general sigh of relief from the “people who write about Ruby” community.&lt;/p&gt;

&lt;p&gt;Singleton classes are often taught as just an odd bit of syntax. Well, as three odd bits of syntax:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Using &lt;code class=&quot;highlighter-rouge&quot;&gt;def self.foo&lt;/code&gt; to create a “class method”&lt;/li&gt;
  &lt;li&gt;Being able to arbitrarily use any object in a method definition, as in &lt;code class=&quot;highlighter-rouge&quot;&gt;def x.foo&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Direct access to the singleton class via &lt;code class=&quot;highlighter-rouge&quot;&gt;class &amp;lt;&amp;lt; self ... end&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But these are all tightly related and are more than merely an odd bit of syntax, they are a solution to a particular language design problem.&lt;/p&gt;

&lt;p&gt;Specifically, the problem of how to have methods attach to a class in a pure Object-Oriented language.&lt;/p&gt;

&lt;h2 id=&quot;the-problem-of-class-methods&quot;&gt;The Problem of Class Methods&lt;/h2&gt;

&lt;p&gt;Because of the way Ruby classes work, it’s a challenge to attach a method to a specific class and not all classes or all instances of a class.&lt;/p&gt;

&lt;p&gt;In a pure Object-Oriented language like Ruby or Smalltalk, everything is an object. Classes themselves are objects – a class is an instance of the class &lt;code class=&quot;highlighter-rouge&quot;&gt;Class&lt;/code&gt; (writing about this stuff leads to tongue twisters pretty quickly).&lt;/p&gt;

&lt;p&gt;There’s a little bit of a logical tangle here: classes are “merely” objects, yes, but classes are also a special kind of object. Within Ruby class objects are unique in that they can create other objects – the instances of that class.&lt;/p&gt;

&lt;p&gt;A Ruby class is created by the keyword &lt;code class=&quot;highlighter-rouge&quot;&gt;class&lt;/code&gt; followed by an object name (with an optional superclass), and then inside the class definition, you typically define a bunch of methods. The first fun fact here is that the class name, as we discussed in &lt;a href=&quot;https://noelrappin.com/blog/2023/10/better-know-a-ruby-thing-22-constants/&quot;&gt;a previous Better Know&lt;/a&gt;, is a constant, and the value of that constant is the instance of the class &lt;code class=&quot;highlighter-rouge&quot;&gt;Class&lt;/code&gt; created by Ruby when the class is defined. The methods defined in the body of the class become instance methods of the class.&lt;/p&gt;

&lt;p&gt;To oversimplify somewhat, when Ruby interprets a &lt;code class=&quot;highlighter-rouge&quot;&gt;def&lt;/code&gt; statement, Ruby takes the resulting method and adds it to an instance variable of the surrounding class &lt;code class=&quot;highlighter-rouge&quot;&gt;Class&lt;/code&gt; which contains a table of all instance methods. Ruby then uses that table when looking up methods at run time when they are called.&lt;/p&gt;

&lt;p&gt;This is all well and good, or at least it’s reasonably transparent to a Ruby developer, but it’s missing a common object-oriented feature, the “class method” (or in some languages, “static method”).&lt;/p&gt;

&lt;p&gt;Instance methods are called with regular instances as receivers. But it’s also useful to have methods that are attached to the class itself. These contain behavior for the class itself, typically things like alternate constructors, or data shared by all instances, and so on.&lt;/p&gt;

&lt;p&gt;What you want is a way to be able to define a method called, say, &lt;code class=&quot;highlighter-rouge&quot;&gt;User.from_data&lt;/code&gt;, and for that method to only belong to the &lt;code class=&quot;highlighter-rouge&quot;&gt;User&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;I’m over explaining here, perhaps, but I want to get across just how odd the method call &lt;code class=&quot;highlighter-rouge&quot;&gt;User.from_data&lt;/code&gt; is in Ruby. In this case &lt;code class=&quot;highlighter-rouge&quot;&gt;from_data&lt;/code&gt; is not an instance method of &lt;code class=&quot;highlighter-rouge&quot;&gt;Class&lt;/code&gt; (the way that &lt;code class=&quot;highlighter-rouge&quot;&gt;new&lt;/code&gt; is an instance method of &lt;code class=&quot;highlighter-rouge&quot;&gt;Class&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;from_data&lt;/code&gt; method needs to specifically attach to the exact instance representing the class &lt;code class=&quot;highlighter-rouge&quot;&gt;User&lt;/code&gt; and &lt;em&gt;only&lt;/em&gt; to that instance. This is different from normal instance method behavior, a normal instance method attaches to all instances. This is the kind of thing that makes perfect sense when you read the line of code calling &lt;code class=&quot;highlighter-rouge&quot;&gt;User.from_data&lt;/code&gt;, and only gets complicated when you have to worry about how to implement that behavior.&lt;/p&gt;

&lt;p&gt;So, you need a method that only attaches to a single object, rather than to all instances of a class. This is a weird thing – if you aren’t talking about instances of the class &lt;code class=&quot;highlighter-rouge&quot;&gt;Class&lt;/code&gt;, it would sound kind of absurd.&lt;/p&gt;

&lt;p&gt;Like, &lt;code class=&quot;highlighter-rouge&quot;&gt;abs&lt;/code&gt; is a method of &lt;code class=&quot;highlighter-rouge&quot;&gt;Integer&lt;/code&gt;, and it applies to all integers. It would be odd if you only wanted &lt;code class=&quot;highlighter-rouge&quot;&gt;abs&lt;/code&gt; to be callable for, like, the number &lt;code class=&quot;highlighter-rouge&quot;&gt;37&lt;/code&gt;. Lots of Ruby tutorials go out of their way to explain how to attach methods to individual instances, but in practice 99.99% of the time the use case is for individual instances of &lt;code class=&quot;highlighter-rouge&quot;&gt;Class&lt;/code&gt;. Which is to say, for class methods.&lt;/p&gt;

&lt;p&gt;What it comes down to is that the &lt;code class=&quot;highlighter-rouge&quot;&gt;User&lt;/code&gt; instance of class is expected to behave differently from an &lt;code class=&quot;highlighter-rouge&quot;&gt;Address&lt;/code&gt; instance of class in a way that is not true of, say the &lt;code class=&quot;highlighter-rouge&quot;&gt;String&lt;/code&gt; &lt;code class=&quot;highlighter-rouge&quot;&gt;&quot;x&quot;&lt;/code&gt; and the &lt;code class=&quot;highlighter-rouge&quot;&gt;String&lt;/code&gt; &lt;code class=&quot;highlighter-rouge&quot;&gt;&quot;y&quot;&lt;/code&gt;, and so there’s more of a need for unique methods attached to each individual class.&lt;/p&gt;

&lt;p&gt;So, to make class methods work, you need some way to attach a method to an individual instance – not an “instance method” in the way that every instance of a given class has the ability to call that method, but a “method of an instance” in the sense that only one instance knows about and can call that method.&lt;/p&gt;

&lt;p&gt;Let’s look at how other languages address this problem.&lt;/p&gt;

&lt;h2 id=&quot;python-and-smalltalk&quot;&gt;Python and Smalltalk&lt;/h2&gt;

&lt;p&gt;In Python, new classes are (by default) instances of &lt;code class=&quot;highlighter-rouge&quot;&gt;type&lt;/code&gt;. Python objects are essentially just key/value stores where the values can be either regular values or callable objects (methods). You can attach a callable object to any instance variable, but there is language support (via the &lt;code class=&quot;highlighter-rouge&quot;&gt;@classmethod&lt;/code&gt; decorator) for attaching a callable object specifically to a &lt;code class=&quot;highlighter-rouge&quot;&gt;type&lt;/code&gt;, making it relatively easy to define class methods, and possible, but more challenging, to attach methods to arbitrary instances. A side effect of this setup is that Python, unlike Ruby, has a single namespace combining instance values and method names, and also unlike Ruby, makes a distinction between “accessing a value”, which you do without parentheses, and “calling a method” which has parentheses.&lt;/p&gt;

&lt;p&gt;In Smalltalk… well, Smalltalk is confusing and I’m not 100% sure I’ve got this. Here’s a stab. A new class in Smalltalk is an instance of a “metaclass”, and each class gets its very own metaclass. So a Smalltalk class called &lt;code class=&quot;highlighter-rouge&quot;&gt;User&lt;/code&gt; is actually the only instance of a metaclass called &lt;code class=&quot;highlighter-rouge&quot;&gt;User class&lt;/code&gt;. The metaclass is itself an instance of a class called &lt;code class=&quot;highlighter-rouge&quot;&gt;Metaclass&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When you create a new class in Smalltalk, you just create the class, the metaclass is created for you by the system. What we’ve been calling “class methods” are actually instance methods of the metaclass. In syntax, you call using the class as the receiver (as in &lt;code class=&quot;highlighter-rouge&quot;&gt;User fromData: data&lt;/code&gt;), and the system internally directs the call to the method of the metaclass.&lt;/p&gt;

&lt;p&gt;In Smalltalk, you interact with your code through an code browser that is part of the system. The Smalltalk code browser mashes the class and metaclass together in the interface, so you see “class methods” as a kind of method you can create on the class, but behind the scenes the method is attached to the metaclass, and the Smalltalk system makes sure that uses of class methods are looked up in the associated metaclass.&lt;/p&gt;

&lt;p&gt;Where this gets weird – if you didn’t think it was already weird – is that &lt;code class=&quot;highlighter-rouge&quot;&gt;Metaclass&lt;/code&gt;, being a class, has its own metaclass, which, circularly, is an instance of &lt;code class=&quot;highlighter-rouge&quot;&gt;Metaclass&lt;/code&gt;. Don’t stare at that sentence too long, you’ll hurt your eyes.&lt;/p&gt;

&lt;p&gt;The upshot is that arbitrary Smalltalk instances can’t have methods attached to them, but individual classes effectively can have methods attached to them through the expedient of a metaclass of which that class is the only instance, and the development environment blends the class and metaclass together seamlessly enough that you don’t normally need to worry about the existence of the metaclass. It all just works.&lt;/p&gt;

&lt;h2 id=&quot;ruby-singleton-classes&quot;&gt;Ruby Singleton Classes&lt;/h2&gt;

&lt;p&gt;Ruby has elements of both the Python and Smalltalk solutions – like Python, Ruby allows methods to be added to any arbitrary instance, not just classes. Like Smalltalk, it does this with the addition of an additional class-like object. Unlike Smalltalk, the Ruby class-like object is not a parent of the class, it’s an attribute of the class.&lt;/p&gt;

&lt;p&gt;In Ruby, every object that is instantiated is represented internally by basically three things:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A table of instance values&lt;/li&gt;
  &lt;li&gt;A pointer to the class of the object, used for method lookup&lt;/li&gt;
  &lt;li&gt;A pointer to a unique class for that instance, called the “singleton class” of the object, also used for method lookup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first two items are pretty consistent among Object-Oriented languages, the third one is the way in which Ruby manages to store methods on an individual class.&lt;/p&gt;

&lt;p&gt;The “singleton class” is an actual Ruby class – meaning that it is an instance of the class &lt;code class=&quot;highlighter-rouge&quot;&gt;Class&lt;/code&gt;. The singleton class is created automatically by Ruby and it is unique to that instance – two different instances of the same class will get different singleton classes, which you can see with code like this:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;001&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Fred&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;002&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Barney&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;003&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;singleton_class&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#&amp;lt;Class:#&amp;lt;String:0x000000011eacefd0&amp;gt;\&amp;gt;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;004&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;singleton_class&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#&amp;lt;Class:#&amp;lt;String:0x000000011e4ea6c8&amp;gt;\&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I think – but I’m not 100% sure – that the singleton class is eagerly created when new class objects are created, but for other objects is only created lazily when requested. As a result, the actual performance overhead is minimal, since 99.99% of objects won’t ever create a singleton class.&lt;/p&gt;

&lt;p&gt;Since the singleton class is a class, you can define methods inside it. And the important part of why singleton classes work is that the singleton class is the first place Ruby looks for a method. In other worlds, when you call a method like &lt;code class=&quot;highlighter-rouge&quot;&gt;user.first_name&lt;/code&gt;, Ruby will look in the singleton class for the specific &lt;code class=&quot;highlighter-rouge&quot;&gt;user&lt;/code&gt; instance first, and will only look for an instance method of the class if there is no matching method in the singleton class.&lt;/p&gt;

&lt;p&gt;If the receiver of the method is a class name, as in &lt;code class=&quot;highlighter-rouge&quot;&gt;User.create_from_data&lt;/code&gt;, the first place Ruby looks is the singleton class of the instance – in this case the instance is &lt;code class=&quot;highlighter-rouge&quot;&gt;User&lt;/code&gt;. This is a class method, that’s how class methods work.&lt;/p&gt;

&lt;p&gt;The fact that &lt;code class=&quot;highlighter-rouge&quot;&gt;User&lt;/code&gt; is a class is incidental here – Ruby always looks at the singleton class, it’s just that there’s very little incentive to use a singleton class unless the receiver is a class, there’s hardly any other case where calling a method on an specific instance is valuable.&lt;/p&gt;

&lt;h2 id=&quot;rubys-syntax&quot;&gt;Ruby’s syntax&lt;/h2&gt;

&lt;p&gt;There are at least three ways to define a method in a Ruby singleton class. Two of these are syntactic shortcuts that tell Ruby that defined methods should be placed in a singleton class.&lt;/p&gt;

&lt;p&gt;The one you are most likely to see is a variation on the normal &lt;code class=&quot;highlighter-rouge&quot;&gt;def&lt;/code&gt; syntax to create a method. The method name is prefixed with an object reference, just as though it was a method call. This is most often seen as a way to define class methods:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create_from_data&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;self.&lt;/code&gt; before the method name tells the Ruby parser that the method doesn’t go where it would otherwise go (in the containing class) instead the method is stored in the singleton class for &lt;code class=&quot;highlighter-rouge&quot;&gt;self&lt;/code&gt;, which, given that &lt;code class=&quot;highlighter-rouge&quot;&gt;self&lt;/code&gt; is referenced inside a class and outside a method, means that &lt;code class=&quot;highlighter-rouge&quot;&gt;self&lt;/code&gt; is the &lt;code class=&quot;highlighter-rouge&quot;&gt;User&lt;/code&gt; class. Again, this is a class method. Older versions of Ruby didn’t allow &lt;code class=&quot;highlighter-rouge&quot;&gt;self&lt;/code&gt; there, so you used to see the class name directly, as in &lt;code class=&quot;highlighter-rouge&quot;&gt;def User.create_from_data&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But this mechanism is not limited to classes – this is the kind of contrived example I was alluding to earlier.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Fred&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Barney&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;shout&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;Hey &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;upcase&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;shout&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; &quot;Hey FRED&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;shout&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; No Method Error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In this case, we’re using the same thing to tell Ruby to place the &lt;code class=&quot;highlighter-rouge&quot;&gt;shout&lt;/code&gt; method in the singleton class for &lt;code class=&quot;highlighter-rouge&quot;&gt;x&lt;/code&gt;. There’s nothing special about &lt;code class=&quot;highlighter-rouge&quot;&gt;x&lt;/code&gt;, it’s just an ordinary instance, but we can still define a method that applies to &lt;code class=&quot;highlighter-rouge&quot;&gt;x&lt;/code&gt; and only &lt;code class=&quot;highlighter-rouge&quot;&gt;x&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The second syntax is a little less common:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create_from_data&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The syntax here is &lt;code class=&quot;highlighter-rouge&quot;&gt;class &amp;lt;&amp;lt; OBJECT&lt;/code&gt;, where “OBJECT” can be any Ruby object. The semantics is that until you hit an &lt;code class=&quot;highlighter-rouge&quot;&gt;end&lt;/code&gt; keyword, all method definitions inside the &lt;code class=&quot;highlighter-rouge&quot;&gt;class &amp;lt;&amp;lt;&lt;/code&gt; statement are sent to the singleton class of the object on the right of the &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;&amp;lt;&lt;/code&gt;, in this case &lt;code class=&quot;highlighter-rouge&quot;&gt;self&lt;/code&gt; which references the &lt;code class=&quot;highlighter-rouge&quot;&gt;User&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;Again, there’s nothing special about &lt;code class=&quot;highlighter-rouge&quot;&gt;self&lt;/code&gt; here, and you can use &lt;code class=&quot;highlighter-rouge&quot;&gt;class &amp;lt;&amp;lt;&lt;/code&gt; with any arbitrary object.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Fred&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Barney&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;shout&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;Hey &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;upcase&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;shout&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The typical use case for &lt;code class=&quot;highlighter-rouge&quot;&gt;class &amp;lt;&amp;lt;&lt;/code&gt; is having a lot of class methods and you want to define them together without repeating &lt;code class=&quot;highlighter-rouge&quot;&gt;def self.&lt;/code&gt; on each one.&lt;/p&gt;

&lt;p&gt;I’m not sure what temperature take this is, but I prefer &lt;code class=&quot;highlighter-rouge&quot;&gt;def self.foo&lt;/code&gt; to &lt;code class=&quot;highlighter-rouge&quot;&gt;class &amp;lt;&amp;lt; self&lt;/code&gt; for much the same reason that I prefer &lt;code class=&quot;highlighter-rouge&quot;&gt;private def foo&lt;/code&gt; – it places important information about the method call at the point of the method declaration, not at some arbitrary point above the method declaration.&lt;/p&gt;

&lt;p&gt;You can also get at the singleton class with the method &lt;code class=&quot;highlighter-rouge&quot;&gt;singleton_class&lt;/code&gt;, so you could do something like this:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Fred&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;x_singleton&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;singleton_class&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;x_singleton&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;define_method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:shout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;HEY &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ruby has a shortcut here, the method &lt;code class=&quot;highlighter-rouge&quot;&gt;define_singleton_method&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Fred&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;define_singleton_method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:shout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;HEY &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Honestly, I think I like &lt;code class=&quot;highlighter-rouge&quot;&gt;x.define_singleton_method(:foo)&lt;/code&gt; more than &lt;code class=&quot;highlighter-rouge&quot;&gt;def x.foo&lt;/code&gt; for non-class objects on the grounds that it’s more explicit, but that’s a pretty weak opinion, using either should be quite rare.&lt;/p&gt;

&lt;h2 id=&quot;the-eternal-question-why-is-ruby-like-this&quot;&gt;The eternal question… Why is Ruby like this?&lt;/h2&gt;

&lt;p&gt;I’ll be frank and admit I’m not really sure why the Smalltalk solution wasn’t used. In practice, Ruby’s behavior is very similar to Smalltalk, it’s hard to imagine another scenario where you’d reasonably want to pin an method on a particular instance, it seems like you could make metaclasses just be a part of how classes work – Ruby already does special work when classes are created.&lt;/p&gt;

&lt;p&gt;My guess is that it’s at least partially a workaround for the fact that Smalltalk has a built-in IDE and Ruby doesn’t. There’s no special syntax to create regular classes in Smalltalk, you just send a message to the system in the code browser saying that a new class should be created.&lt;/p&gt;

&lt;p&gt;In Smalltalk, you don’t need bonus syntax to denote a class method because the way Smalltalk works, you just enter them in the part of the IDE that is marked “class methods” and the system puts them in the right place. Most of the time you don’t even have to know that the metaclass exists.&lt;/p&gt;

&lt;p&gt;But Ruby’s a scripting language and doesn’t have a built-in IDE, so there needs to be some kind of syntax to denote a “class method”. From that point, using dot syntax makes sense. Why that syntax is &lt;code class=&quot;highlighter-rouge&quot;&gt;def self.foo&lt;/code&gt; and not, say, &lt;code class=&quot;highlighter-rouge&quot;&gt;self.def foo&lt;/code&gt;, I’m not sure. I assume that parsing &lt;code class=&quot;highlighter-rouge&quot;&gt;self.def&lt;/code&gt; would be complicated, but I don’t really know.&lt;/p&gt;

&lt;p&gt;And that’s the singleton class, a Very Ruby Way to get an important OO feature and still keep the object system consistent.&lt;/p&gt;</content><author><name>Noel Rappin</name></author><category term="ruby" /><category term="better_know" /><category term="dynamic_rubyist" /><category term="singleton" /></entry><entry><title type="html">Object Constellations</title><link href="https://noelrappin.com/2024/11/object-constellations/" rel="alternate" type="text/html" title="Object Constellations" /><published>2024-11-20T00:00:00+00:00</published><updated>2024-11-20T00:00:00+00:00</updated><id>repo://blog.collection/_blog/2024-11-20-object-constellations.md</id><content type="html" xml:base="https://noelrappin.com/2024/11/object-constellations/">&lt;p&gt;&lt;a href=&quot;https://noelrappin.com/blog/2024/09/how-not-to-use-static-typing-in-ruby/&quot;&gt;Last time&lt;/a&gt;, I talked about ways to use dynamic typing to manage objects and business logic in your code. Doing so involves leaning into the object system, going beyond just “one class for each noun” and creating objects to model different states within the business logic directly.&lt;/p&gt;

&lt;p&gt;In a basic Object-Oriented design, you might have an object called &lt;code class=&quot;highlighter-rouge&quot;&gt;User&lt;/code&gt;. This object, by itself, represents the entire concept of a user within the system. In this design, specific states of a user — admin, unauthorized, deleted, subscriber, what have you — are all represented by the single class &lt;code class=&quot;highlighter-rouge&quot;&gt;User&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That’s one way to model users. But you could also have the &lt;code class=&quot;highlighter-rouge&quot;&gt;User&lt;/code&gt; class be a home for the underlying data and manage some or all of the state of a user by creating wrapper classes like &lt;code class=&quot;highlighter-rouge&quot;&gt;AdminUser&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;UnauthorizedUser&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;DeletedUser&lt;/code&gt;… even &lt;code class=&quot;highlighter-rouge&quot;&gt;NullUser&lt;/code&gt;. At this point, the idea of a “user” is now spread among multiple classes. I’ve started calling this an “object constellation”, feel free to call it whatever you want.&lt;/p&gt;

&lt;h2 id=&quot;why-the-constellation-works&quot;&gt;Why the Constellation Works&lt;/h2&gt;

&lt;p&gt;Ruby, like many object oriented languages, is “polymorphic”. In Ruby terms, “polymorphic” means that result of a method call, like &lt;code class=&quot;highlighter-rouge&quot;&gt;user.access_allowed?&lt;/code&gt;, is resolved by the class of &lt;code class=&quot;highlighter-rouge&quot;&gt;user&lt;/code&gt; at run-time, and that &lt;code class=&quot;highlighter-rouge&quot;&gt;user&lt;/code&gt; could be of any class that implements &lt;code class=&quot;highlighter-rouge&quot;&gt;access_allowed?&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As a result, you can kind of model Ruby’s message passing as a huge &lt;code class=&quot;highlighter-rouge&quot;&gt;if&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;case&lt;/code&gt; statement:&lt;/p&gt;

&lt;p&gt;In pseudo-code, when you call &lt;code class=&quot;highlighter-rouge&quot;&gt;receiver.foo&lt;/code&gt;, you can model what Ruby does like this:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;receiver&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Integer&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;call&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;#foo&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;call&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;#foo&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;so&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;on&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(Ruby’s internals look different, it’s closer to a hash look up than a case statement, the point is that you can model message passing as the result of a  &lt;code class=&quot;highlighter-rouge&quot;&gt;case&lt;/code&gt; statement.)&lt;/p&gt;

&lt;p&gt;This model of method passing is why you typically are advised not to have case statements where the clauses are classes – you’re advised to just dispatch the call polymorphically to the correct method… because the case statement and the dispatch are essentially the same thing. (In Ruby, you might sometimes use the case statement to avoid monkey patching core classes…)&lt;/p&gt;

&lt;p&gt;The flip side is that you can also model conditional logic in the case system, and dispatch calls the same way.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;receiver&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;call&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NillUser&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;#foo&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;admin?&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;call&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;AdminUser&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;#foo&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;so&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This suggests that if we can map the clauses of this case statement to classes that have identically named methods, we can map the entire conditional to a polymorphic method call. When we do this, we use the object system to hold onto conditional state.&lt;/p&gt;

&lt;h2 id=&quot;null-objects&quot;&gt;Null Objects&lt;/h2&gt;

&lt;p&gt;A concrete example involves calls to &lt;code class=&quot;highlighter-rouge&quot;&gt;nil&lt;/code&gt; — in this case your constellation consists of two classes: the “real” class and the null class.&lt;/p&gt;

&lt;p&gt;You may have a lot of code that looks like this:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;company&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;nil?&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;do_something&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# error related&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;do_something&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# real object related&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If we think about each branch of that &lt;code class=&quot;highlighter-rouge&quot;&gt;if&lt;/code&gt; statement mapping to a different class, that would mean we have two classes. Our regular &lt;code class=&quot;highlighter-rouge&quot;&gt;Company&lt;/code&gt;, and then a… well, a &lt;code class=&quot;highlighter-rouge&quot;&gt;NullCompany&lt;/code&gt;. These types are related, in that they should have the same API, but one of them has real business logic, and the other is basically an empty shell.&lt;/p&gt;

&lt;p&gt;There are a bunch of ways to accomplish having these two classes in Ruby. One way is to have a factory class that replicates the conditional logic and returns one class or the other. Then put a method in each class – with the same name – with the corresponding logic:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Company&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;maybe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;company_or_nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;company_or_nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;nil?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NullCompany&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;company_or_nil&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;do_something&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# doing something&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;NullCompany&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;do_something&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We’ve ow replaced the conditional:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;company&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;nil?&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;do_something&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;with a polymorphic method call…&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Company&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;maybe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;company&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;do_something&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Okay, I’ve replaced a simple conditional with some object-oriented hand-waving. Why?&lt;/p&gt;

&lt;p&gt;Reasons in favor:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The through line of the logic in original method is clearer, (I mean, not in this trivial example, but in a real problem) this is especially true if there are a lot of different branches to the conditional.&lt;/li&gt;
  &lt;li&gt;It’s easier to test, especially easier to test the null object behavior in isolation.&lt;/li&gt;
  &lt;li&gt;If this check is done repeatedly, it ensures consistency in how the check is handled. This is especially valuable for complex status checks. (I find this to be an underrated benefit — there’s a strong tendency to make the checks more thorough if you are only typing them once…)&lt;/li&gt;
  &lt;li&gt;These small classes have a way of accumulating behavior in useful ways.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reasons against:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The logic is now more distributed, if you want to see what happens on the error condition, you have to seek out the &lt;code class=&quot;highlighter-rouge&quot;&gt;NullUser&lt;/code&gt; class. You have to get used to it. (I will say that as editor support has improved, this gets easier).&lt;/li&gt;
  &lt;li&gt;This works interestingly with static typing tools, since you now have a lot of variables that are, say, the union type of &lt;code class=&quot;highlighter-rouge&quot;&gt;User || NullUser&lt;/code&gt;. I guess, though, you can then specify &lt;code class=&quot;highlighter-rouge&quot;&gt;User&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;NullUser&lt;/code&gt; to get interesting type safety.&lt;/li&gt;
  &lt;li&gt;There’s some Ruby-specific sort of weirdness with null objects and logical truth and falsity. In Ruby, the only things that are logically false are &lt;code class=&quot;highlighter-rouge&quot;&gt;nil&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;false&lt;/code&gt;, meaning that &lt;code class=&quot;highlighter-rouge&quot;&gt;nil&lt;/code&gt; is logically false, but &lt;code class=&quot;highlighter-rouge&quot;&gt;User.maybe(nil)&lt;/code&gt; returning a &lt;code class=&quot;highlighter-rouge&quot;&gt;NilUser&lt;/code&gt; instance isn’t logically false, which can lead to subtle bugs. This is a manageable problem, but you have to pick a way to manage it. (For instance, you can define a &lt;code class=&quot;highlighter-rouge&quot;&gt;nillish?&lt;/code&gt; method and use that for your checks.)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;object-and-shadow-object&quot;&gt;Object and Shadow Object&lt;/h2&gt;

&lt;p&gt;In the Null Object pattern you have a constellation of two classes: the “full-featured” class, like our &lt;code class=&quot;highlighter-rouge&quot;&gt;Company&lt;/code&gt;, and then a parallel “shadow” classes. The shadow class, like &lt;code class=&quot;highlighter-rouge&quot;&gt;NullCompany&lt;/code&gt;, represents a genuine logical state in the system, but one in which you are representing the absence of something or, more generally, an incomplete form of something. Specifically, the shadow class typically does not need to access the data that the full-featured class does.&lt;/p&gt;

&lt;p&gt;The shadow object has the same API as the original object but has custom business logic that manages the lack of data.&lt;/p&gt;

&lt;p&gt;Once you start looking for this pattern, it’s all over…&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;UnauthenticatedUser&lt;/code&gt; — if you have logic that allows logged in users and non-logged in users, having the default current user be of class &lt;code class=&quot;highlighter-rouge&quot;&gt;UnauthentcatedUser&lt;/code&gt; lets you easily manage logic for users that haven’t created accounts. You can even tie a cookie to a specific instance of &lt;code class=&quot;highlighter-rouge&quot;&gt;UnauthenticatedUser&lt;/code&gt; and allow that user to have at least some of the features of your app.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;NonexistentFile&lt;/code&gt; — perhaps this one auto-creates the file before attempting to write to it, or maybe it just returns an empty string before you try to read from it.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;DeletedUser&lt;/code&gt; — my user was “friends” with another user who has been archived, this class lets me treat all those users together in one loop. There could even be multiple shadow objects here, &lt;code class=&quot;highlighter-rouge&quot;&gt;BlockedUser&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;RenamedUser&lt;/code&gt;, etc, all of which contain the edge case logic needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Almost any Rails &lt;code class=&quot;highlighter-rouge&quot;&gt;belongs_to&lt;/code&gt; relationship has a plausible case for a shadow object to model the case where the other side of the object doesn’t exist or doesn’t exist yet. (The &lt;a href=&quot;https://github.com/wohlgejm/null_associations&quot;&gt;null_associations&lt;/a&gt; gem does this automatically, but I’m not sure if it’s current.)&lt;/p&gt;

&lt;p&gt;And the shadow object can have real logic — I’m doing a project that describes gem dependencies across multiple apps and gems by parsing &lt;code class=&quot;highlighter-rouge&quot;&gt;Gemfile.lock&lt;/code&gt;. Not every gem in our ecosystem puts a lockfile in the repository, so I have &lt;code class=&quot;highlighter-rouge&quot;&gt;ExplicitLockfile&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;ImplicitLockfile&lt;/code&gt;. The &lt;code class=&quot;highlighter-rouge&quot;&gt;ImplicitLockfile&lt;/code&gt; is a shadow object which attempts to fake the dependency tree information by inferring it from the &lt;code class=&quot;highlighter-rouge&quot;&gt;.gemspec&lt;/code&gt;. It makes the code much clearer.&lt;/p&gt;

&lt;h2 id=&quot;superset-objects&quot;&gt;Superset Objects&lt;/h2&gt;

&lt;p&gt;There are other constellation patterns that don’t depend on shadow objects.&lt;/p&gt;

&lt;p&gt;For example there could be a data class that is the center of the constellation with multiple classes in the constellation that wrap the data class and therefore have access to the underlying data. Typically, this pattern happens when each wrapper class represents a different state or condition of the underlying data.&lt;/p&gt;

&lt;p&gt;For example, you might have a &lt;code class=&quot;highlighter-rouge&quot;&gt;User&lt;/code&gt; class that might have some role within the system. You could set that up as a constellation of classes:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;as_role&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;AdminUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;admin?&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;TeamLeadUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;team_lead?&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;OrdinaryUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserInRole&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;edit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;can_edit?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;edit_button&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;can_edit?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# HTML to draw a button&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AdminUser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;UserInRole&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;can_edit?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TeamLeadUser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;UserInRole&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;can_edit?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;team&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;team&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OrdinaryUser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;UserInRole&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;can_edit?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then any time you need to do something with a user that depends on their role, rather than repeating the cascade of ifs or whatever, you can just do something like this:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;as_role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;edit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;edit&lt;/code&gt; method calls &lt;code class=&quot;highlighter-rouge&quot;&gt;can_edit?&lt;/code&gt; meaning that the actual behavior of &lt;code class=&quot;highlighter-rouge&quot;&gt;edit&lt;/code&gt; is dependent on the role of the user.&lt;/p&gt;

&lt;p&gt;This code prevents repeating that if/case logic to determine the type of user by allowing downstream logic to call &lt;code class=&quot;highlighter-rouge&quot;&gt;user.as_role&lt;/code&gt; to determine the user role, and then &lt;code class=&quot;highlighter-rouge&quot;&gt;edit&lt;/code&gt; to actually do the edit if it’s authorized.&lt;/p&gt;

&lt;p&gt;The critical point here is that while the role objects have access to the user data, methods like &lt;code class=&quot;highlighter-rouge&quot;&gt;edit&lt;/code&gt; are only available on the role object, so you must use &lt;code class=&quot;highlighter-rouge&quot;&gt;as_role&lt;/code&gt; to check the role logic before trying&lt;/p&gt;

&lt;h2 id=&quot;so&quot;&gt;So…&lt;/h2&gt;

&lt;p&gt;In the name of keeping this one under 2500 words, I’m going to stop here. You should try this technique (granted that this is over simplifying, and, oh, I’m probably going to need to post a more complete example).&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Find a conditional logic that you use more than once in your app. Maybe it’s a &lt;code class=&quot;highlighter-rouge&quot;&gt;nil&lt;/code&gt; check, maybe it’s a big &lt;code class=&quot;highlighter-rouge&quot;&gt;case&lt;/code&gt; statement.&lt;/li&gt;
  &lt;li&gt;Copy that conditional to a factory method that returns a different class for each branch of the conditional.&lt;/li&gt;
  &lt;li&gt;Create the new classes. They may need to be delegators or have constructors that connect back to the original data class.&lt;/li&gt;
  &lt;li&gt;Move the logic from each branch into a method of the corresponding class, give the methods the same name.&lt;/li&gt;
  &lt;li&gt;Replace the original conditional with a call to the factory method and a call to the new method name.&lt;/li&gt;
  &lt;li&gt;Find other examples of the same conditional logic and repeat steps 4 and 5.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Be careful with &lt;code class=&quot;highlighter-rouge&quot;&gt;nil?&lt;/code&gt; and implicit boolean checks.&lt;/p&gt;

&lt;p&gt;Try it once, see if you like it.&lt;/p&gt;</content><author><name>Noel Rappin</name></author><category term="locally_sourced" /><category term="ruby" /><category term="dynamic" /><category term="typing" /></entry></feed>