<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>zverok&apos;s space</title>
    <description>I don&apos;t build systems. I imagine them, then write them.</description>
    <link>https://zverok.space/</link>
    <atom:link href="https://zverok.space/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Sun, 18 Jan 2026 17:39:43 +0000</pubDate>
    <lastBuildDate>Sun, 18 Jan 2026 17:39:43 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>It is 2026; where were we?</title>
        <description>&lt;p&gt;Most of the time, I try to keep this blog/mailing list to thoroughly thought-through articles about software development: mostly, programming languages in general and Ruby in particular.&lt;/p&gt;

&lt;p&gt;But after it was dormant for quite some time, this one will be a bit more personal. And a lot less coherent.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;For those who just wandered into this blog post on my site, or those who have subscribed to my &lt;a href=&quot;http://zverok.substack.com/&quot;&gt;mailing list&lt;/a&gt; and forgot what that was about (I get it! I wasn’t writing anything for months!), just a quick reminder: I am a software developer and writer from Ukraine, currently serving in the military. You can look up my personal site for some of my &lt;a href=&quot;https://zverok.space/writing/&quot;&gt;writing&lt;/a&gt;, &lt;a href=&quot;https://zverok.space/projects/&quot;&gt;projects&lt;/a&gt;, and &lt;a href=&quot;https://zverok.space/talks/&quot;&gt;talk recordings&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This blog/mailing list didn’t see me writing with any kind of consistency for the best part of the last two years. There are reasons for that – and there are reasons to write again, or at least try to.&lt;/p&gt;

&lt;p&gt;This text is my attempt to reflect on &lt;em&gt;what to write&lt;/em&gt; in these weird times, to focus on what still makes sense to write.&lt;/p&gt;

&lt;p&gt;But first, I want to mention a few things I &lt;em&gt;did&lt;/em&gt; write last year. Ruby-related and not (so much not, they aren’t about software development at all).&lt;/p&gt;

&lt;h2 id=&quot;writing-in-2025&quot;&gt;Writing in 2025&lt;/h2&gt;

&lt;p&gt;Christmas 2025 saw the release of Ruby &lt;strong&gt;4.0&lt;/strong&gt;: a new “major” version of my primary programming language for 20+ years; the one for which I am a proud (if minor) committer. While I didn’t participate much in the language development this year (other than an occasional issue tracker discussion), this December I traditionally made an &lt;strong&gt;&lt;a href=&quot;https://rubyreferences.github.io/rubychanges/4.0.html&quot;&gt;annotated changelog&lt;/a&gt;&lt;/strong&gt; for it.&lt;/p&gt;

&lt;p&gt;I do for the eighth year in a row and have covered the changes in the freshly released version since Ruby 2.6 (and also expanded it to couple of prior versions and made a bird’s-eye-view &lt;a href=&quot;https://rubyreferences.github.io/rubychanges/evolution.html&quot;&gt;Evolution&lt;/a&gt; page, briefly listing “all notable changes since Ruby 2.0”).&lt;/p&gt;

&lt;p&gt;The “annotated changelog” only covers the language (syntax, semantics, and API) changes. While the latest version doesn’t have as much as “major” version change would imply (Ruby doesn’t follow semantic versioning, and this year’s number change is just to celebrate the language’s 30th birthday, according to Matz), the format I set to myself – every change tested, explained, and demonstrated – required quite a lot of work. As usual, going through the new and updated stuff made me notice where the behaviors weren’t clear or documentation was lacking; to make this part of the work more visible, I decided to mention it in the &lt;a href=&quot;https://rubyreferences.github.io/rubychanges/4.0.html#side-effects-of-the-changelog&quot;&gt;afterword of the changelog&lt;/a&gt; since this year.&lt;/p&gt;

&lt;p&gt;Aside from this work – and a couple of stray blog posts, which I’ll explain a bit later – I spent my rare free time translating modern Ukrainian poetry, written by authors serving in the Ukrainian Armed Forces, for my personal project &lt;strong&gt;&lt;a href=&quot;https://7uapoems.substack.com/&quot;&gt;7uapoems&lt;/a&gt;&lt;/strong&gt;. I rarely mention it in my programming-related blog, but maybe some of my readers would be interested – I constantly find myself surprised by how many technical people are actually into poetry and literary writing in general.&lt;/p&gt;

&lt;p&gt;Speaking of which (but probably not of interest to 90% of the readers), this year I also finished my new Ukrainian novel in Ukrainian, and saw my &lt;a href=&quot;https://zverok.space/writing/jona/&quot;&gt;first published one&lt;/a&gt; gaining some readership.&lt;/p&gt;

&lt;p&gt;At least, that explains how my writing muscle was occupied throughout the year (or, what my graphomania was channeled into, if you will).&lt;/p&gt;

&lt;h2 id=&quot;not-writing-in-2025&quot;&gt;Not writing in 2025&lt;/h2&gt;

&lt;p&gt;I still feel uneasy about all but abandoning the blog/mailing list – and that at the times when it gained some solid audience. You, my reader: my several hundred of mailing list subscribers. Which is, of course, a tiny number in modern megalomaniac internet, but enough for me to feel “I am talking to real people, not just screaming into the void.”&lt;/p&gt;

&lt;p&gt;So, why DON’T I actually talk?&lt;/p&gt;

&lt;p&gt;The above section might’ve been &lt;em&gt;some&lt;/em&gt; explanation: I wrote a lot last year, just in other media/on other topics, but it is not the full explanation.&lt;/p&gt;

&lt;p&gt;Another one, is of course, &lt;strong&gt;the war.&lt;/strong&gt; I am a serving officer, and while I am &lt;em&gt;serving&lt;/em&gt; as a software developer, too – this still limits my time and free mind resources for writing. It was a hard year for Ukraine, for many reasons which you either already know or didn’t care enough to learn. My team, though, achieved quite a lot – but the details I, for the time being, can’t share. Neither the particular things we’ve done, nor insights related to those achievements. My primary language/domain in this role is different now (Python and data processing), but on the projects, practices, and achievements my lips are sealed.&lt;/p&gt;

&lt;p&gt;But even this is not the full explanation for a dormant blog, to be honest.&lt;/p&gt;

&lt;p&gt;The thing is, I have only two “regimes” of writing. The first one, which, unfortunately, activates pretty rarely, is “write down a quick &lt;a href=&quot;2024-10-06-poetry.html&quot;&gt;idea&lt;/a&gt;/&lt;a href=&quot;2024-11-16-elixir-pipes.html&quot;&gt;experiment&lt;/a&gt;/&lt;a href=&quot;2024-10-21-global_functions.html&quot;&gt;explanation&lt;/a&gt; that you just thought of.” The trick here is to match a reasonably small idea with enough free time to write it down immediately. Otherwise, it might turn into something that I either consider too trivial, or, on the contrary, something that should lead to a deeper investigation and more detailed explanation of the matter, or more comprehensive implementation of an idea.&lt;/p&gt;

&lt;p&gt;Because my second – and, unfortunately, primary – regime of writing is to follow some long train of thought, or overarching idea, or explaining a reasonably large domain. My head just works like that. When I still wrote poems (which is a short and self-sufficient form), I, for decades, only wrote them in “cycles” of ten to twenty texts, which might’ve been diverse in tone and style but still were part of some internal plan and intention.&lt;/p&gt;

&lt;p&gt;And when you do this – planning to write a series on some topic/area – with programming-related blog posts, the things get complicated. Without enough feedback (or, with a lot of negative feedback) it is quite easy to decide to just drop the topic. My &lt;a href=&quot;/writing/blog/&quot;&gt;blog&lt;/a&gt; is shamefully full of posts that claim to be the start of a large series – even including something like “Part 1” in its title; there is also a fair share of “course correction” reflective posts (like &lt;a href=&quot;2024-05-12-updates.html&quot;&gt;this one&lt;/a&gt;) to claim “I understood what I want to write about now,” with no follow-ups.&lt;/p&gt;

&lt;p&gt;Sometimes there are “series” that I planned and executed till the end. Sometimes, again, without enough feedback or understanding whether there is an audience (like &lt;a href=&quot;/spellchecker.html&quot;&gt;Rebuilding The Spellchecker&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Other times, I pull this up successfully. The last of such successes was the cheeky-named “&lt;a href=&quot;2023-10-02-syntax-sugar.html&quot;&gt;Useless Ruby sugar&lt;/a&gt;” in autumn 2023 – which, actually, started as an article of the “first type”: quickly triggered response to someone on the Reddit, to explain why “useless syntactic sugar &lt;em&gt;they&lt;/em&gt; add to &lt;em&gt;our&lt;/em&gt; language” is not that “useless,” and grew into two months of weekly structured posts.&lt;/p&gt;

&lt;p&gt;This one accidentally became quite successful – and at the moment, it felt as if both my expertise &lt;em&gt;and&lt;/em&gt; my way of looking at things were important to this success. As was my fascination with the story of a language evolution – a programming language evolution, in this case, but taken from “how it makes us think and write.”&lt;/p&gt;

&lt;p&gt;So, after that, I had this idea about a corpus of texts – blog posts or even a book, maybe? – on Ruby’s evolution. Some crossover between that &lt;a href=&quot;https://rubyreferences.github.io/rubychanges/&quot;&gt;changelog&lt;/a&gt; I maintain and the “‘useless’ sugar” series, to try to tell a coherent story of how the language takes shape and changes with time. I did a few approaches to the idea (&lt;a href=&quot;2024-06-14-method-evolution.html&quot;&gt;1&lt;/a&gt;, &lt;a href=&quot;2024-07-01-optional-args.html&quot;&gt;2&lt;/a&gt;, &lt;a href=&quot;2024-07-26-range-evolution.html&quot;&gt;3&lt;/a&gt;)… and quite quickly understood there just isn’t a vibrant audience for this kind of content. At least, not among the readers who stumble upon my texts – maybe somewhere in academia? – anyway. In the autumn of 2024 I reviewed the series/book idea critically – and then dropped it altogether.&lt;/p&gt;

&lt;p&gt;Then, in 2025, I had some vague yet persistent chain of thought about the similarities of working with code and working with texts in human language; the width and depth of it seemed promising (and speaking to a wider programming community than Ruby-related texts). It never caught enough wind though, two posts boldly named &lt;a href=&quot;2025-06-11-code-war-notes-week1.html&quot;&gt;week 1&lt;/a&gt; and &lt;a href=&quot;2025-06-21-code-war-notes-week2.html&quot;&gt;week 2&lt;/a&gt; (plus an unfinished draft of week 3) is as far as I got before understanding that the overarching idea is too broad to speak to anybody in particular. Maybe, it should’ve been approached differently. Maybe, it will.&lt;/p&gt;

&lt;p&gt;Or maybe it all – my attention to language nuance, my belief that good software grows from well-written “phrases” –  will become irrelevant, and quite soon.&lt;/p&gt;

&lt;p&gt;For the last couple of years, people seem to stop writing code by themselves.&lt;/p&gt;

&lt;h2 id=&quot;a-brief-diversion-my-views-on-ai&quot;&gt;A brief diversion: my views on AI&lt;/h2&gt;

&lt;p&gt;I wouldn’t try to make a comprehensive write-up on everything I think on the large area of tech that goes under the “AI” shortcut. I was always interested in the technological/scientific part of it, frequently worked in adjacent areas (hell, my first full-time software development employment in 2003 was several months working on a small startup that tried to apply those days neural networks to stock market prediction or something); and, while not being an insider of this part of the industry, I have &lt;em&gt;a whole lot&lt;/em&gt; of thoughts and opinions formed through past decades.&lt;/p&gt;

&lt;p&gt;But for the sake of this particular text, it is enough to say this: I still feel kind of childish wonder in front of a technological part of it, of the &lt;em&gt;intelligence illusion&lt;/em&gt; which emerges from the simple math. And unlike &lt;a href=&quot;https://www.baldurbjarnason.com/&quot;&gt;Baldur Bjarnason&lt;/a&gt;, whose &lt;a href=&quot;https://illusion.baldurbjarnason.com/&quot;&gt;book title&lt;/a&gt; I am quoting here and for whose opinions I have a lot of respect, I use “illusion” here in a less-than-derogatory sense: when you look at a purely technical side of things, it is somewhat fascinating how the unpredictability of the human thought can be imitated by matrix multiplication.&lt;/p&gt;

&lt;p&gt;At the same time, I am as much horrified as everybody else with the economic and societal consequences of what the current iteration of unregulated mega-corporations, hype cycle, and malevolent power players do to our world using those technologies. I am not sure whether it is a “bubble” that will “pop” at some point; I will abstain from any attempt to extrapolate “where is it going”  – but overall, the “AI future” doesn’t look bright.&lt;/p&gt;

&lt;p&gt;One thing, though, seems to me if not inevitable then at least highly likely: the change that LLMs and generative AI bring to all kinds of “informatics-related” work (any kinds of creation, maintaining and changing any “intellectual property,” be it art, news, or programming) is pretty similar to industrialization of the early XX century.&lt;/p&gt;

&lt;p&gt;I mean that, for example, software development, even in huge international corporations, was more like “craft” just a few years ago. I say “craft” not as a synonym of “art,” but in a medieval sense: hand work of highly and narrowly qualified professionals, with guilds, apprenticeship, and significant impact of personal traits on the end product. Even if the “guilds” grew into extremely large international corporations, making personal impact in many particular cases as negligible as “cog in the machine,” the impact still stayed personal.&lt;/p&gt;

&lt;p&gt;Now, it is about to change – already changes. LLMs take a role of the “machines” in an information-related “production.” No analogy is perfect, but this one seems to give a lot of predictions and explanations. Like: “aren’t machine-made products inferior?” – many might be, but in a lot of situations, 100 shitty plastic chairs produced in a day will be preferred to one sturdy and polished wooden one, produced in a week. Or: what will happen to the “craftsman” of yesteryear? In imaginary factories, some of them will become engineers, others – low-valued machine operators. Some will continue to do what they’ve done: there is still room for handiwork in today’s world: producing artisan unique items, or, vice versa, quick-fixing broken tables for local grandmas; or, semi-manually creating R&amp;amp;D prototypes for future machine implementation. But there will be much less demand for this kind of work.&lt;/p&gt;

&lt;p&gt;To be clear, this is not an optimistic, progress-embracing view. Industrialization of the XX century saw a lot of unfairness – due, in part, to shifting the balance of employer/employee relationship. This time, it might be much worse, considering &lt;em&gt;*gestures vaguely at everything*&lt;/em&gt;. We are used to using “Luddite” as a dismissive term for “those who don’t understand the virtues of progress.” But we – some of us, anyway; many of us, probably – are about to learn &lt;em&gt;what was their point.&lt;/em&gt; In a hard way.&lt;/p&gt;

&lt;h2 id=&quot;maybe-writing-in-2026&quot;&gt;Maybe writing in 2026&lt;/h2&gt;

&lt;p&gt;The lengthy rant above might seem out of place in this personal post (I acknowledge that it is a shortened version of a separate “what I think about AI” article draft). However, the changing landscape directly affects the topics that make me passionate enough to write programming-related texts.&lt;/p&gt;

&lt;p&gt;My main idea always was thinking about code writing as I think about text writing. Thinking at the level of a singular phrase, that affects the way the underlying structures are built and how the language is used and evolves. The optics I sometimes like to call “lucid code.”&lt;/p&gt;

&lt;p&gt;Even before the advance of LLMs, this way of thinking was a hard sell. The frequent consensus is that architecture and high-level structure of the program are the things that matter, while the code underneath is “just boring details.” Surprisingly, though, my ways of thinking, when battle-tested on complicated production projects, tend to show its benefits for velocity and maintainability. With years, I learned to explain it better.&lt;/p&gt;

&lt;p&gt;But the tectonic change in the industry (if it is not an illusion, of course!) might make it all irrelevant. Well, the approaches I use still can be rebranded to being &lt;em&gt;token-efficient&lt;/em&gt; to flourish in LLM era… But for now, I feel an irrational aversion for this angle.&lt;/p&gt;

&lt;p&gt;For the time being, I stick to my “craft.” And still will try to write about it – about the ways of &lt;em&gt;thinking in code&lt;/em&gt; that make me productive, and about languages, tools, and approaches that adhere to these ways. And I still hope to have an audience for that, and to find my place in the ever-changing industry.&lt;/p&gt;

&lt;p&gt;I’ll probably stick to deeper, not wider topics in my upcoming texts (if any will come); focus on sharing pragmatic parts of my experience rather than philosophical outlooks on the general matters of programming. Though, TBH, I have one almost-finished “philosophical” text that’d marinated in my drafts since late summer… So maybe I’ll finish it some time in the coming weeks.&lt;/p&gt;

&lt;p&gt;But after that, I hope to dedicate some efforts to writing about my approaches to testing and various nuances of experience I gathered by working on test suits of several large produces (including developing and publishing my own &lt;a href=&quot;https://github.com/zverok/moarspec&quot;&gt;test helpers library&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;So, till the &lt;a href=&quot;http://zverok.substack.com/&quot;&gt;next time&lt;/a&gt;. Hopefully, it will be soon.&lt;/p&gt;

&lt;p&gt;And please &lt;strong&gt;&lt;a href=&quot;https://war.ukraine.ua/&quot;&gt;support Ukraine&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
</description>
        <pubDate>Sun, 18 Jan 2026 00:00:00 +0000</pubDate>
        <link>https://zverok.space/blog/2026-01-18-upd.html</link>
        <guid isPermaLink="true">https://zverok.space/blog/2026-01-18-upd.html</guid>
        
        
        <category>personal</category>
        
      </item>
    
      <item>
        <title>Notes on code, text, and war. Week 2: If code is text, then what?</title>
        <description>&lt;p&gt;Last time, I’ve highlighted “treating code as text” idea as something that I want dedicate a few posts to. Let’s talk in generalities some more this time.&lt;/p&gt;

&lt;div class=&quot;blurb&quot;&gt;
  &lt;p&gt;&lt;strong&gt;OK, if we look at code as a text, then what?&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;If this metaphor-based approach – bringing the mental model of one domain into another – is useful, it should have some consequences. What are we gaining by using the culture’s experience with natural language texts in software development?&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;For now, I want to briefly outline some consequences and then expand on them, hopefully, in future posts.&lt;/p&gt;

&lt;p&gt;The code is frequently compared to a cultural artifact, and most of the time, as a juxtaposition. Sometimes, this juxtaposition seeks reconciliation, as in literate programming. Sometimes it is antagonistic, asserting the superiority of “precise engineering work” over something as intangible as culture. However, even those who compare coding to writing as a compliment frequently go for it as an emotional argument for coding being a creative work, too. In those discussions, you might hear that “good code reads almost like poetry”, meaning a synonym for generic “beauty,” something that is “joyful” to read or write.&lt;/p&gt;

&lt;p&gt;Honestly, I am the last person to frown at defining code as “craft” and at statements that it might bring “joy.” I have been writing code (and texts) all my life, and I am in it because I really like to do it. But also, I know that to become better in coding, as well as in writing, “looking for joy” and “listening to your heart” is not enough. At least, for me.&lt;/p&gt;

&lt;p&gt;And so we can use the comparisons for something other than emotional effect. We can translate our experience with texts to working with code.&lt;/p&gt;

&lt;p&gt;Say, &lt;strong&gt;on the level of a single phrase&lt;/strong&gt; (the level which Ruby’s expressiveness taught me to emphasize and cherish), we can talk about choice of “words”, the thesaurus that the language provides, and its expansions with the names we chose to become “words” in our code. We can talk about idioms and set phrases that are typical for language, and explore how they might be thought-provoking or thought-obscuring, or become thought-terminating cliches.&lt;/p&gt;

&lt;p&gt;We can talk about even seemingly insignificant aspects, such as &lt;em&gt;layout&lt;/em&gt; and &lt;em&gt;punctuation&lt;/em&gt;, and how line-breaking choices might emphasize or muddle the structure of what is said.&lt;/p&gt;

&lt;p&gt;An answer to a primitive question like “assume &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt; is a list of users, how would you produce a list of user names from it?” and its typical solutions in every language and community might expose, like a drop of water, a whole ocean of approaches and values.&lt;/p&gt;

&lt;p&gt;In software development, many of those questions are often dismissed as “bikeshedding” and addressed with prescriptivist rules and tools that rewrite the code to adhere to a “common style.” This is frequently done without a distinction between genuine typos/cleanup (e.g., fixing a stray space where it is not intended to be) and enforcing mechanistic uniformity, where the style previously emphasized the flow of thought. The rise of “opinionated” (see: non-configurable, “because there is nothing to discuss”) code formatters is part of this problem.&lt;/p&gt;

&lt;p&gt;Next, &lt;strong&gt;on the level of code units&lt;/strong&gt; that structure and organize your phrases (methods/functions, modules, classes), we might, again, think about what structuring of &lt;em&gt;texts&lt;/em&gt; teaches us.&lt;/p&gt;

&lt;p&gt;Visible &lt;strong&gt;page&lt;/strong&gt; of text/code seems an important concept here: how much can you comprehend without scrolling/jumping around? The question is frequently neglected – despite being of utter importance for the human perception – in favor of, again, prescriptivistic rules, like requiring to structure everything in extremely small methods, or code layouting conventions that prefer very short lines with roughly one “word” (method call, argument, etc.) per line.&lt;/p&gt;

&lt;p&gt;We might examine how well-written, comfortable-to-read texts are broken into sections, how important headers and call-outs emphasize key thoughts, while build-ups to them are packed together into paragraphs, and useful yet tangential facts are extracted into footnotes and sidenotes. We might discuss how section and paragraph sizes differ depending on genre and intended effect.&lt;/p&gt;

&lt;p&gt;Moving to higher levels, such as &lt;strong&gt;the whole project or a large component&lt;/strong&gt;, we might again look at the experience of organizing large groups of heterogeneous texts.&lt;/p&gt;

&lt;p&gt;Obviously, most modern software projects (save maybe for some narrow and specific libraries) don’t provide a linear narrative like a book does. But I like to think about organizational principles of large magazines and modern media outlets as an inspiration for some ideas, without mindless borrowing of what is irrelevant for the sake of extending the metaphor.&lt;/p&gt;

&lt;p&gt;Think of the difference in styles to match the purpose, but under the same guidebook.&lt;/p&gt;

&lt;p&gt;Or, about the variety of roles and competences in text work. A genius editor is not necessarily a good writer; a literary editor and a managing editor who overlooks a rubric and the editor in chief being all different “hats” (even if not always different people). There are reporters, analysts, and fact-checkers, and they work in an elaborate dance of creating a whole. It is not hard to find equivalents for those roles in software projects. It is not that hard to imagine how people with distinct skill sets and temperaments find a matching role that makes them most efficient and productive. How they are keeping a common schedule, contributing their angles of view, and working towards the general integrity of the software project/publication.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;This is a weird year to speak of any integrity, though. Ukrainians know it like no one else, ever bitter and growing more bitter by the day.&lt;/p&gt;

&lt;p&gt;For a week now, the world seems to be forgetting about Ukraine completely. (Forgetting even more than before, I mean.) You know what I am talking about, what’s on the news and social networks everywhere.&lt;/p&gt;

&lt;p&gt;I wouldn’t share my opinions on the incredibly tangled Middle East politics.&lt;/p&gt;

&lt;p&gt;But I want you to imagine, if just for a moment, how Ukrainians feel about the world’s “integrity” when we read about Western joint efforts to protect against an attack by a hundred Shahed kamikaze drones. The same Shahed Iran is being supplied to Russia. The same Shaheds that, for the last months, have attacked Ukrainian cities in quantities of &lt;strong&gt;two to four hundred per night&lt;/strong&gt;. Frequently, many nights in a row.&lt;/p&gt;

&lt;p&gt;No Western aircraft ever tried to help intercept them in our skies. The idea of using our “partners’” aviation for this task (for cities that are very far from the front lines!) was briefly entertained in discussions last year, and then swiftly and decisively abandoned by everybody with a great sigh of relief.&lt;/p&gt;

&lt;p&gt;They were also very public and clear about this, not even maintaining some strategic public ambiguity, which in itself might deter Russia’s boldest moves for some time. And they do it all the time: after the first rumors of a possible “ceasefire” this May (which were baseless but sparked some “what next” discussions), most Western governments were very quick to emphasize that no peacekeeping contingent from &lt;em&gt;their&lt;/em&gt; country would be possible on Ukrainian soil. As clearly as possible, for Russia to hear and take into account. Again: not that we expected any! But those rushed assurances are a message in themselves, too.&lt;/p&gt;

&lt;p&gt;It is like they perceive Russia with some split-brain malfunctioning: it is, at the same time, entirely harmless for them (so there is no need to help Ukraine push it back decisively) but also extremely powerful (so the “provocation” as moderate as UK or French plane helping to patrol Ukrainian skies 1000 km from the active battles might cause catastrophic consequences). I once theorized that &lt;a href=&quot;https://zverok.space/writing/onwar/2024-04-09-1777789363545379180.html&quot;&gt;Western perception of Russia is kinda religious&lt;/a&gt;, like that of an unpredictable yet vengeful god.&lt;/p&gt;

&lt;p&gt;Sorry for this (probably pointless) venting.&lt;/p&gt;

&lt;p&gt;It is somewhat hard to build a deliberate thought structure, trying to translate years of thinking about being better in our professional skills in this world. In a world that seems to be in agreement in its collective drive to fall apart into incoherent fragments, and the whole industry of IT, the industry about building and maintaining systems, realigns with this fragmented vision.&lt;/p&gt;

&lt;p&gt;I am well aware that my notes are fragmented, too. But I hope they will eventually amount to some cohesive vision. The vision that will be helpful to fellow programmers, if only in small ways.&lt;/p&gt;

&lt;p&gt;See you next time.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;Thank you for reading. Please support Ukraine with your donations and lobbying for military and humanitarian help. &lt;a href=&quot;https://war.ukraine.ua/&quot;&gt;Here&lt;/a&gt;, you’ll find a comprehensive information source and many links to state and private funds accepting donations.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you don’t have time to process it all, donating to the &lt;a href=&quot;https://savelife.in.ua/en/&quot;&gt;Come Back Alive&lt;/a&gt; foundation is always a good choice.&lt;/strong&gt;&lt;/p&gt;

&lt;iframe src=&quot;https://zverok.substack.com/embed&quot; width=&quot;480&quot; height=&quot;320&quot; style=&quot;border:1px solid #EEE; background:white;&quot; frameborder=&quot;0&quot; scrolling=&quot;no&quot;&gt;&lt;/iframe&gt;
</description>
        <pubDate>Sat, 21 Jun 2025 00:00:00 +0000</pubDate>
        <link>https://zverok.space/blog/2025-06-21-code-war-notes-week2.html</link>
        <guid isPermaLink="true">https://zverok.space/blog/2025-06-21-code-war-notes-week2.html</guid>
        
        
        <category>code-as-text</category>
        
      </item>
    
      <item>
        <title>Notes on code, text, and war. Week 1: Believing in text</title>
        <description>&lt;div class=&quot;blurb&quot;&gt;
  &lt;p&gt;&lt;strong&gt;Writing texts is the most useful and fruitful metaphor for software development.&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;After 25+ years of writing software and at least as much of writing texts, this is the one thing that I have become sure about.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;This also means that &lt;em&gt;reading, writing, editing, and layouting&lt;/em&gt; concrete chunks of code is a central activity of software development. Not planning, not scheming, not calculating and measuring. All of those take their place—like in writing complex texts, they do—but the most direct and useful experience we can have is still that of getting our hands on the code.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Does this point of view stand in the age of LLMs? I believe that not only it does, but becomes stronger and more productive. The LLM breakthrough (however many ethical and ideological concerns we have about it; I have plenty) is a direct consequence of the vast number of texts written by human civilization. It is also a direct cause of the explosive increase of this number. Whether you intend to resist it, utilize it, denounce it, or ride it, &lt;em&gt;looking at everything as text&lt;/em&gt; is a useful tool of though.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Depending on your experience, work environment and cultural background, the focus on code-as-text might seem painfully trivial. Or it might seem screamingly irrelevant and wrong. But bear with me for some time. I think that I am able to show many practical implications of focusing on this. Some non-obvious implications, too.&lt;/p&gt;

&lt;p&gt;For this, I want to write a series of (relatively) short posts/notes: after a long silence in my blog, I have things to say. Things that might provide others with some new perspectives/ideas. Or, at least, irritate in an entertaining way!&lt;/p&gt;

&lt;p&gt;Previously, my &lt;a href=&quot;https://zverok.space/writing/blog/&quot;&gt;writing&lt;/a&gt; was mostly focused on Ruby. (Save for a few long digressions to “open data” direction—the direction that was almost entirely cannibalized by global LLMs now. So what, fewer things to think about.) I am still quite fond of the language; I still use it daily. But what I like to think about now is much less Ruby-centric, though in many aspects inspired by my &lt;em&gt;experience&lt;/em&gt; of writing in it for many years, and even participating in the language development.&lt;/p&gt;

&lt;p&gt;“Experience” is an important word for me. And frequently definitive for the topic I chose.&lt;/p&gt;

&lt;p&gt;We write and read to share the experience. Not in a narrow professional sense, but in a broader cultural one: My text allows you to &lt;em&gt;experience&lt;/em&gt; what I lived and you didn’t, and vice versa.&lt;/p&gt;

&lt;p&gt;Prose shares the experience of stories. Poetry shares subtler experiences of feeling, inspiration, and insight.&lt;/p&gt;

&lt;p&gt;Utilitarian texts—like journalistic, academic, or “texts” in programming languages—share the experience of &lt;em&gt;understanding&lt;/em&gt; something. Not the knowledge/understanding itself, but the experience of understanding from a particular person. Alongside the experience of being this person, with their education, habits, expertise, and personality.&lt;/p&gt;

&lt;p&gt;Say, here is my experience: I am a lifelong software developer, primarily writing in Ruby (since at least 2004), and for some time, I actively &lt;a href=&quot;https://zverok.space/ruby.html&quot;&gt;participated&lt;/a&gt; in language development and documentation. But also, I am an author of prose and poetry, with some magazine publications, festivals, and a relatively popular &lt;a href=&quot;https://zverok.space/writing/jona/&quot;&gt;novel&lt;/a&gt; published just last year. But also, I am Ukrainian, living through war, serving in the Armed Forces, with some combat experience and current duties &lt;em&gt;not completely unlike&lt;/em&gt; my civilian ones (though nothing more precise can be said of them).&lt;/p&gt;

&lt;p&gt;Obviously, I compartmentalize my experiences to some extent. Sometimes to the extent of switching to almost unrelated personas. But for a deeper look, this is an illusion. Obviously, each “persona” draws from the experience of the other. I plan novels not unlike the way I plan software projects. I care for expressive code and constantly think about how others achieve that (and how they fail), not unlike in my literary work.&lt;/p&gt;

&lt;p&gt;And, as a person living through a genocidal invasion of the former empire, and knowing what led to it, and observing how the world responds to it, well… I think I became more attentive to the weight of proper representation of information, to cognitive biases codified in texts (and software), to consequences of exaggerations and omissions, overstatements and understatements. Even in the most routine work during software development.&lt;/p&gt;

&lt;p&gt;In my 2024 EuRuKo talk “Seven things I know after many years of development” (&lt;a href=&quot;https://youtu.be/r9EQjBPU474&quot;&gt;video&lt;/a&gt;; &lt;a href=&quot;https://zverok.space/blog/2025-01-27-7things-euruko.html&quot;&gt;transcript article&lt;/a&gt;), I tried to blend my war/programming experiences. People say it was insightful for them. Maybe they are just being polite.&lt;/p&gt;

&lt;p&gt;Anyway, during the work on the talk, I came to the emphasis on &lt;strong&gt;truth and clarity&lt;/strong&gt; as the primary goal of code writing. Even the most mundane code writing.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Not directly related to the text above, but related to the living experience of Ukrainians, here is a truth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Almost exactly two years ago, on June 6, 2023, Russians blew up the Kakhovka Dam&lt;/strong&gt; on Ukraine’s main river Dnipro, draining the huge Kakhovka reservoir and flooding some 600+ square kilometers of populated land and natural reserves.&lt;/p&gt;

&lt;p&gt;Even the approximate number of victims is still unknown: on the left bank of the river, still occupied by Russians, there were no efforts neither for immediate relief nor for subsequent recovery of bodies. The environmental, humanitarian, and economic damage of this man-made event is said to be comparable to a nuclear explosion.&lt;/p&gt;

&lt;p&gt;There were many texts written since about the cause, consequences, and aftermath of the catastrophe. A major part of international ones—immediate news as well as subsequent assessments, political statements and philosophical essays alike—had a strong tendency to diminish the effect, or shift the blame towards “natural causes,” or “harsh realities of war.”&lt;/p&gt;

&lt;p&gt;There were no strong consequences for Russia, other than the—usual and used-to—”public condemnation” (and even that expressed in no clear terms). There were no new sanctions other than those already planned, regularly slightly expanded, and regularly easily circumvented.&lt;/p&gt;

&lt;p&gt;Even today, English Wikipedia &lt;a href=&quot;https://en.wikipedia.org/wiki/Destruction_of_the_Kakhovka_Dam&quot;&gt;prefers&lt;/a&gt; the passive voice of “the dam was breached” with a cautious explanation like “many experts say it was Russians, but Russians deny that, so in the interest of Neutral Point Of View that’s all that can be said.”&lt;/p&gt;

&lt;p&gt;This is just one of the many examples that might explain why I am so obsessed with truth and clarity, and how what we write and what we read structures our understanding. In texts and code alike.&lt;/p&gt;

&lt;p&gt;We’ll get back to the code at some point. But right now, I want to highlight one text written in the aftermath of the Kakhovka Dam explosion.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://store.ukrainer.net/en/product/book-flood/&quot;&gt;&lt;img src=&quot;/img/2025-06-11/the-flood.jpg&quot; alt=&quot;&amp;quot;The Flood&amp;quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is a book that was announced just a few days ago, on a sad anniversary. &lt;strong&gt;“&lt;a href=&quot;https://store.ukrainer.net/en/product/book-flood/&quot;&gt;The Flood&lt;/a&gt;“&lt;/strong&gt; is a narrative non-fiction, “a journey into the heart of the Khersonian steppes, a story of loss and resilience seen through the eyes of witnesses.” It is written in English by Yevhen Lyr—a brilliant Ukrainian writer, translator, educator, and volunteer who was there in the immediate aftermath of the tragedy, one of those dealing with its consequences.&lt;/p&gt;

&lt;p&gt;Now, he is a soldier.&lt;/p&gt;

&lt;p&gt;Many of us are.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Sometimes, I still have people in comments and Internet discussions of my technical articles wondering how we can live like that. Meaning, how tragedies of the scale of Kakhovka Dam, or nightly shelling of cities, or constant deaths of people we know, might coexist with what looks like “normal life”: like this blog, or new Ukrainian books, or concerts, or parks, or new cafes. How do people move forward with their lives? (Sometimes those questions are compassionate, and sometimes they are sarcastic or reek of suspicion: this so-called “war” shouldn’t be that bad if you write about programming or visit a concert?)&lt;/p&gt;

&lt;p&gt;Here is one more truth for you: you get used to anything, given enough time.&lt;/p&gt;

&lt;p&gt;A human being can only stay in shock for so long. And “so long” here is hours and days, not months and years. Then (given at least &lt;em&gt;some&lt;/em&gt; pockets of pretend normalcy), you get back to who you were before. Not completely, but to some visible extent.&lt;/p&gt;

&lt;p&gt;Almost every day, we have awful “anniversaries” like Kakhovka Dam ones: death, destruction, occupation, loss. From the past three years of the full-scale invasion. From the previous eight years of hybrid war. From hundreds of years of colonial attempts to erase our identity.&lt;/p&gt;

&lt;p&gt;And it is not just anniversaries of past events, of course. It continues and becomes worse.&lt;/p&gt;

&lt;p&gt;Last weekend, my home city, Kharkiv, where my family still lives, survived the largest combined aerial attack—kamikaze drones + various types of rockets + gliding bombs—since the beginning of war. Just last night, our capital Kyiv survived another one.&lt;/p&gt;

&lt;p&gt;And so on. And so on.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;img src=&quot;/img/2025-06-11/kharkiv.jpg&quot; alt=&quot;Kharkiv attack on Jun 11, 2025. by Nakypilo media&quot; /&gt;&lt;/p&gt;

  &lt;p&gt;Oh wait. I wrote the previous paragraph yesterday but didn’t finish the article just then. This night, Kharkiv was again attacked by 17 large kamikaze drones at once, targeting populated areas. At least sixty people are injured, at least two dead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And: we still live here. Soldiers and civilians, grown-ups and children alike. “&lt;a href=&quot;https://zverok.space/writing/onwar/2024-03-26-1772149722955804995.html&quot;&gt;Our cities are too inconvenient for Western media&lt;/a&gt;” is one of my short texts that I wrote over the last three years. &lt;a href=&quot;https://zverok.space/writing/onwar/&quot;&gt;There are several more&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Trying to write those texts—trying to explain &lt;em&gt;anything, anything at all&lt;/em&gt;—sometimes also feels absurd. And sometimes as just a way to distract myself. But I am still writing. Less of “explanations of our life for foreigners” (who aren’t that interested anyway), more of code, and my literary work, and my &lt;a href=&quot;https://7uapoems.substack.com/&quot;&gt;poetry translations&lt;/a&gt;, and now, hopefully: this series of cross-topic texts.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;What does it all have to do with the “code as text” concept? Maybe nothing. Maybe everything.&lt;/p&gt;

&lt;p&gt;The humane part of the software development craft always appealed to me more than anything else. But this part exposes itself only over time. Let’s put it this way: a reasonably dedicated developer can implement any reasonable idea in any first way they think of. Like, you can convey urgent news or a fresh thought in the roughest, most incoherent words, and still pass the information.&lt;/p&gt;

&lt;p&gt;But for an idea to live, for a project to evolve, to preserve &lt;em&gt;velocity&lt;/em&gt; and &lt;em&gt;maintainability&lt;/em&gt;, you need something more. Some ways for &lt;em&gt;all of it to continue make sense&lt;/em&gt;. I say it in the most pragmatic way: a growing and ever-changing system like a software project needs a way to maintain coherence and not fall apart. Even developing the most pedestrian features and performing the most mundane refactorings, I never stop thinking about the ways we &lt;em&gt;convey messages&lt;/em&gt; and &lt;em&gt;preserve truth&lt;/em&gt;. I continue to learn. I continue to try to share my experience.&lt;/p&gt;

&lt;p&gt;I don’t know if what I am saying &lt;em&gt;makes sense&lt;/em&gt; right now.&lt;/p&gt;

&lt;p&gt;But I’ll be trying it to, with more pragmatic, hopefully regular, chapters, roughly related to the “code as text” topic. And to war.&lt;/p&gt;

&lt;p&gt;See you next time.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;Thank you for reading. Please support Ukraine with your donations and lobbying for military and humanitarian help. &lt;a href=&quot;https://war.ukraine.ua/&quot;&gt;Here&lt;/a&gt;, you’ll find a comprehensive information source and many links to state and private funds accepting donations.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you don’t have time to process it all, donating to the &lt;a href=&quot;https://savelife.in.ua/en/&quot;&gt;Come Back Alive&lt;/a&gt; foundation is always a good choice.&lt;/strong&gt;&lt;/p&gt;

&lt;iframe src=&quot;https://zverok.substack.com/embed&quot; width=&quot;480&quot; height=&quot;320&quot; style=&quot;border:1px solid #EEE; background:white;&quot; frameborder=&quot;0&quot; scrolling=&quot;no&quot;&gt;&lt;/iframe&gt;
</description>
        <pubDate>Wed, 11 Jun 2025 00:00:00 +0000</pubDate>
        <link>https://zverok.space/blog/2025-06-11-code-war-notes-week1.html</link>
        <guid isPermaLink="true">https://zverok.space/blog/2025-06-11-code-war-notes-week1.html</guid>
        
        
        <category>code-as-text</category>
        
      </item>
    
      <item>
        <title>Seven things I know after 25 years of development</title>
        <description>&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide1.png&quot; /&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;This is a loose transcript of a keynote I gave at the &lt;a href=&quot;https://2024.euruko.org/&quot;&gt;EuRuKo conference&lt;/a&gt; in September 2024. The video of the talk is &lt;a href=&quot;https://youtu.be/r9EQjBPU474?si=Yc-0ftOTqGu-DhVh&quot;&gt;here&lt;/a&gt;. Unfortunately, I only could talk in recording, but it was a great honor nevertheless. The topic is pretty important to me, so I decided to share a slightly edited transcript for those who prefer text form.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide2.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;I have been writing software for about ~25 years. For 20 of those, I have been using Ruby as my primary language. I’ve made some &lt;a href=&quot;https://zverok.space/ruby.html&quot;&gt;contributions to the language&lt;/a&gt; and &lt;a href=&quot;https://github.com/zverok&quot;&gt;other open source&lt;/a&gt;. I &lt;a href=&quot;https://zverok.space/writing/blog/&quot;&gt;write a lot&lt;/a&gt; about Ruby, and I maintain the &lt;a href=&quot;https://rubyreferences.github.io/rubychanges/&quot;&gt;Ruby Changes&lt;/a&gt; project, which some of you probably heard of.&lt;/p&gt;

&lt;p&gt;I am kind of principal engineer in the US company &lt;a href=&quot;https://hubstaff.com/&quot;&gt;Hubstaff&lt;/a&gt;, but only “kind of,” because…&lt;/p&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide3.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;The most important thing about me is that I am Ukrainian who serve in the Armed Forces. Currently, I am not in a combat position but doing other things that I am better at (and the rest of my activities are happening besides these duties).&lt;/p&gt;

&lt;p&gt;I got into the army not because I am very militaristic or like fighting but because I have a duty to protect my homeland.&lt;/p&gt;

&lt;p&gt;It is the same for many Ukrainians.&lt;/p&gt;

&lt;p&gt;There are all kind of people serving in our Armed Forces currently, and I have a small personal project of &lt;a href=&quot;https://7uapoems.substack.com/&quot;&gt;translating poets&lt;/a&gt; who serve in the army into English.&lt;/p&gt;

&lt;p&gt;So the actual title of the talk is&lt;/p&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide4.png&quot; alt=&quot;Seven things I know after 25 years of development and 10 years of war&quot; /&gt;&lt;/div&gt;

&lt;p&gt;It is still about development and Ruby, but I allowed myself to draw some parallels with my experience as a Ukrainian. I hope it makes the talk richer. Nothing too graphic/disturbing, I promise.&lt;/p&gt;

&lt;p&gt;Let’s get to the matter, those seven things I wanted to share.&lt;/p&gt;

&lt;h2 id=&quot;1-you-outgrow-every-framework&quot;&gt;1. You outgrow every framework&lt;/h2&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide5.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;In any area, not only in tech, we frequently rely on various frameworks and foundations to know what to do and what to expect.&lt;/p&gt;

&lt;p&gt;Say, in international relations, we Ukrainians, for a long time, have relied on being a sovereign country whose security is guaranteed by global powers. Which turned out to be, let’s say, not as solid as we’d hoped.&lt;/p&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide6-1.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;So, as programmers, we frequently rely on frameworks, and as Rubyists, we rely on The Framework, but I am not about to criticize any particular one.&lt;/p&gt;

&lt;p&gt;The point is that frameworks provide us with &lt;a href=&quot;https://www.youtube.com/watch?v=lI77oMKr5EY&quot;&gt;rooms and boxes&lt;/a&gt; to put our code in, and to feel confident, only implementing the business logic.&lt;/p&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide6-2.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;But we all know that eventually, as code grows, we have more concepts, and then even more, some of them provided by your framework, some of them introduced by a team.&lt;/p&gt;

&lt;p&gt;And however we extend it, something always sticks out.&lt;/p&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide7-1.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;And like a fractal, every particular concept starts small and then grows into a framework of its own. Like common nowadays callable object, which is a simple idea. When you introduce it, it might seem like a small step you need to achieve the completeness of the mental model.&lt;/p&gt;

&lt;p&gt;And then there are new requirements and new use cases, and eventually, you have a dozen DSL options to define one.&lt;/p&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide7-2.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;And then something sticks out anyway, not fitting into any of the previous definitions. And if you don’t handle it, you’ll either have very bloated concepts or some garbage bin, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lib/&lt;/code&gt; folder or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/services&lt;/code&gt; folder, and soon nobody is sure what is were.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;To handle that, you should be ready to make your own architectural decision, regardless of the frameworks you rely upon. But there’s a problem.&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;2-patterns-and-methodologies-fail&quot;&gt;2. Patterns and methodologies fail&lt;/h2&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide8.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;The second thing I am sure of is that whole patterns and methodologies fail you and get outdated.&lt;/p&gt;

&lt;p&gt;Like in our Ukrainian experience, the whole international security architecture became a laughing matter, international humanitarian structures proved themselves useless, and human rights defenders defended anything else other than human rights.&lt;/p&gt;

&lt;p&gt;As a developers, when we need to make a decision, we…&lt;/p&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide9-1.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;…might rely on some methodologies, trusted approaches, books, patterns, or funny mnemonics encoding simple principles.&lt;/p&gt;

&lt;p&gt;However, as code grows, the project matures, as the industry develops new opinions, and as challenges change, a lot of doubts and questions emerge.&lt;/p&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide9-2.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;You might learn that inheritance was an idea that is good only in textbooks or that the whole OOP failed us. You start to doubt whether Active Record is a viable approach or whether passive entities and repositories are saner.&lt;/p&gt;

&lt;p&gt;You might be reminded that SOLID is an acronym coined for marketing purposes by one person, and it groups probably useful but vaguely defined and contradictory principles. And in general, for any common approach, there are a lot of arguments about its usefulness or harmfulness (and counter-arguments, and counter-arguments to that).&lt;/p&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide10-1.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;And so we discover for ourselves more high-level guiding principles, like DDD, which follows a very sane idea of isomorphism of the domain and its coding representation. However, it still leaves many blanks in design decisions, such as when a small domain concept requires a big algorithm to implement or when important domain concepts correspond to a small implementation.&lt;/p&gt;

&lt;p&gt;It also introduces a lot of new terms that people love to turn into literal classes, so eventually, you have, besides your MVC classes, also entities, aggregates, bounded contexts… Or, as a &lt;a href=&quot;https://x.com/robsmallshire/status/1055085690260721664&quot;&gt;wise man put once&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide10-2.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;Or we might talk about “hexagonal architecture”, right?&lt;/p&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide10-3.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;…Which is notable for never explaining why is it hexagonal, and for leaving the design decisions about the whole “core” architecture for yourself. (I am not here to say it doesn’t provide some useful concepts.)&lt;/p&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide10-4.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Ultimately, those might help, but eventually, you will be on your own in designing new things. Why is it so?&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;3-the-scale-only-grows-with-time&quot;&gt;3. The scale only grows with time&lt;/h2&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide11.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;Because the scale only grows with time. This is true about many things in the world, and it is also quite a trivial statement, but it has frequently overlooked consequences.&lt;/p&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide12.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;Imagine any Rails app you ever maintained for some time. I think you probably agree that all it did was grow.&lt;/p&gt;

&lt;p&gt;We live in a capitalistic world where you need to run fast to stay relevant. Whether it is some new tech like switching to SPAs, extending your market share to customers of a smaller or bigger scale than before, trying to adjust your market fit without losing old customers, or just linear growth.&lt;/p&gt;

&lt;p&gt;Whatever happened, there was more code, more tables, more endpoints, more tests, more everything.&lt;/p&gt;

&lt;p&gt;As an example, we might look at the &lt;a href=&quot;https://gitlab.com/gitlab-org/gitlab&quot;&gt;GitLab open-source codebase&lt;/a&gt;, which at first looks like your regular Rails app.&lt;/p&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide13-1.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;Initially, it was what? Just a UI for collaboration on top of git.&lt;/p&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide13-2.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;Now if you look through its main folder, you’ll see a bit more things than in a new Rails app, and it into its &lt;a href=&quot;https://gitlab.com/gitlab-org/gitlab/-/tree/master/app?ref_type=heads&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/&lt;/code&gt;&lt;/a&gt; folder, it has a multitude named concepts of its own, and if you’ll open &lt;a href=&quot;https://gitlab.com/gitlab-org/gitlab/-/tree/master/app/services?ref_type=heads&quot;&gt;one of those folders&lt;/a&gt;, you see more, and more, and more. (Note I only got up to “c” letter on the last screenshot.)&lt;/p&gt;

&lt;p&gt;Maybe not all of our projects are at a scale of GitLab, but talking about any commercial project, eventually, you’ll need things like reports, notifications, better role management, payment systems, new APIs, bulk operations, business analytics, and so on and so on.&lt;/p&gt;

&lt;p&gt;The simple yet frequently overlooked consequence I was talking about: &lt;strong&gt;it is perfectly natural that your old ways of handling problems would stop working eventually.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The way of thinking about ten controllers, with four standard methods each, just doesn’t scale to hundreds of non-trivial endpoints.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What do we do then?.. Well, I have a paradoxical answer!&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;4-pay-attention-to-stories&quot;&gt;4. Pay attention to stories&lt;/h2&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide14.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;And my answer is: Pay attention to singular stories. When working with any feature of our software (implementing a new one, adjusting an old one, or trying to understand why it behaves in the wrong way), we always have a narrative to follow: there are some causes and consequences: inputs, outputs, transformations, effects, which can be told linearly.&lt;/p&gt;

&lt;p&gt;But by jumping through layers and conventions and discussing the “bigger picture,” we are frequently losing those particular threads—and then losing the bigger picture.&lt;/p&gt;

&lt;p&gt;A small illustration: once upon a time, I was &lt;a href=&quot;https://github.com/rubocop/rubocop/pull/8071&quot;&gt;trying to add a new feature&lt;/a&gt; to Rubocop (it didn’t work out but for an unrelated reason), and I had this AHA moment when I spent, like, a day trying to understand an algorithm split in 2-3 “simple” line methods, and eventually rewrote it into one, half a screen-sized. Which, though, didn’t match expected “complexity metrics”:&lt;/p&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide15-1.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;Here, for comparison, is the fragment of the &lt;a href=&quot;https://github.com/rubocop/rubocop/blob/v0.85.1/lib/rubocop/comment_config.rb#L44&quot;&gt;original algorithm&lt;/a&gt; (how it looked at the time), but I don’t have a big enough screen to screenshot the whole thing:&lt;/p&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide15-2.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;That’s not about “badly written” Rubocop (it is an awesome software project maintained by extremely competent people), but about some deep difference in the approaches.&lt;/p&gt;

&lt;p&gt;Splitting everything into ultimately small methods and extracting every condition to a predicate is actually a widespread approach in the Ruby community (which the default linters’ complexity thresholds reinforce).&lt;/p&gt;

&lt;p&gt;As a counter opinion, at Hubstaff, we have this fragment in the “small coding rules” document, which underlines the idea of “trying to put everything together first, and impose architecture only after that”:&lt;/p&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide16-1.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;The “single method” is not a key here!&lt;/p&gt;

&lt;p&gt;What IS a key is the point of view from which focusing on “exposing what’s happening here” with code is the main goal. The rough list of principles we follow are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Dense code;&lt;/li&gt;
  &lt;li&gt;Expressive language;&lt;/li&gt;
  &lt;li&gt;Rich &lt;em&gt;thesaurus&lt;/em&gt;;&lt;/li&gt;
  &lt;li&gt;Frequent small rewrites;&lt;/li&gt;
  &lt;li&gt;Attention at the level of a singular page (screen) of code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It leads to the discovery of good names and structures of related concepts—much more reliably than just trying to invent them on the architectural planning stage and fit into the “big picture.”&lt;/p&gt;

&lt;p&gt;But most of the time, there is no single simple “big picture” at all (as anybody who tried to make a class or flow diagram of a mature project will agree).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It is just a bunch of interlinked stories, a garden with paths through it, not a concrete skyscraper. But why are these stories so important?..&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;5-the-goals-are-truth-and-clarity&quot;&gt;5. The goals are truth and clarity&lt;/h2&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide17.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;Because our final goals, as developers and as human beings, are truth and clarity. And it emerges from listening to stories. One might say that “truth” is a weird word when talking about software architecture; it is more from philosophy (or law) domain…&lt;/p&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide18.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;…But my persuasion is that on an intuitive level, all our principles and methodologies are introduced as a way to search for the truth.&lt;/p&gt;

&lt;p&gt;Like OOP was invented in simpler times in the hope of modeling the whole world, and behavior-driven development is a way to honestly describe what is expected from a piece of software before implementing it, and so on.&lt;/p&gt;

&lt;p&gt;Our problem is that we turn any good and imprecise idea into a discipline, formalize it, and then eventually, instead of trying to truthfully mirror the problem, we replace it with rigorously following the rules.&lt;/p&gt;

&lt;p&gt;As a small example, the Ruby community once leveraged the language expressiveness to invent an incredibly powerful testing DSL named RSpec but then eventually persuaded itself that tests shouldn’t be expressive and they should follow some rules of the most primitive code possible:&lt;/p&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide19.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;As a consequence, many Rubyists are taught to write tests that are more verbose than the code it tests, sometimes orders of magnitude more verbose. They hate it rightfully and are forbidden from even dreaming of thinking it could be another way.&lt;/p&gt;

&lt;p&gt;Well, it actually can.&lt;/p&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide20.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;Whether you like this example or hate it, I really hope you can see here a point where the example tells the story of the expected behavior. An attempt to make it work also illustrates the effect the “storytelling” approach has on the thesaurus. To make it work, you’ll need a few new high-level abstractions that are defined once and then improve the life of many spec authors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;However, it is a well-known fact that approaches like this frequently encounter fierce pushback.&lt;/strong&gt; And here lies my sixth thing.&lt;/p&gt;

&lt;h2 id=&quot;6-this-might-be-a-lonely-experience&quot;&gt;6. This might be a lonely experience&lt;/h2&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide21.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;Trying to stick to the true stories might be a very lonely experience. First of all, because people have their “big pictures,” and the first thing they’ll expect from your story is to see how it fits in those.&lt;/p&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide22.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;So, when trying to “sell” the approach that focuses on “what’s really going on here”—be it on code reviews, pair programming, or discussing style guides—one might meet reactions ranging from total indifference to active hostility.&lt;/p&gt;

&lt;p&gt;Because looking for truth is about adjusting your mental models constantly and questioning your own experience. In general, people, especially established professionals and especially-especially software developers, aren’t up for it. I mean, we might change tools every day, right, but internalized persuasions and mechanical habits? Never!&lt;/p&gt;

&lt;p&gt;One might notice that lately, in industry, there is a lot of strong sentiment against “smart code” and expressiveness in general, or, broader, about the unimportance of the particular ways we write code and, as an offspring of it, a strong sentiment against code review practice.&lt;/p&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide23-1.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;I get it, I do.&lt;/p&gt;

&lt;p&gt;In general, when solving problems, people prefer to write and avoid reading and editing. This leads to the sad state of things when the practice of code review turns into a laborious formal duty for both participants (who don’t know what exactly to do about it other than go through the moves) and eventually is badmouthed as a harmful and meaningless way to waste time and establish dominance.&lt;/p&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide23-2.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;But in my view, reading and discussing each other’s code is the only way to make sure the story you are telling makes sense to others, that it doesn’t contradict other stories, and that it doesn’t misuse a common thesaurus to say things that shouldn’t be said that way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is a deeply humane activity, and nobody will persuade me otherwise.&lt;/strong&gt; Which leads me to my last point. Kind of sentimental one.&lt;/p&gt;

&lt;h2 id=&quot;7-never-give-up-seeking-truth&quot;&gt;7. Never give up seeking truth&lt;/h2&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide24.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;Never give up seeking truth, however uncomfortable it is. Search for knowledge. Adjust your worldview. Ask. Rewrite outdated code. Drop faulty hypotheses and unreliable foundations.&lt;/p&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide25.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;Software author is, first of all, a writer. They are a person who stands upright and says: “that’s what I know for now, and that’s my best attempt to explain it.” Having this stance, preferring it to everything else, and hiding behind terms, concepts, and authority are invaluable qualities for long-term project success.&lt;/p&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide26.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;Or, basically, for any long-term human activity success.&lt;/p&gt;

&lt;p&gt;This is the most important thing I learned. Probably not much for a career this long, but that’s where I stand personally and professionally.&lt;/p&gt;

&lt;div class=&quot;talk-slide&quot;&gt;&lt;img src=&quot;/img/2025-01-27-euruko/slide27.png&quot; /&gt;&lt;/div&gt;

&lt;p&gt;Thank you. I hope to see you all in person one day.&lt;/p&gt;

&lt;p&gt;And please, even if not remembering this talk, remember to help Ukraine.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;Thank you for reading. Please support Ukraine with your donations and lobbying for military and humanitarian help. &lt;a href=&quot;https://war.ukraine.ua/&quot;&gt;Here&lt;/a&gt;, you’ll find a comprehensive information source and many links to state and private funds accepting donations.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you don’t have time to process it all, donating to &lt;a href=&quot;https://savelife.in.ua/en/&quot;&gt;Come Back Alive&lt;/a&gt; foundation is always a good choice.&lt;/strong&gt;&lt;/p&gt;

&lt;iframe src=&quot;https://zverok.substack.com/embed&quot; width=&quot;480&quot; height=&quot;320&quot; style=&quot;border:1px solid #EEE; background:white;&quot; frameborder=&quot;0&quot; scrolling=&quot;no&quot;&gt;&lt;/iframe&gt;
</description>
        <pubDate>Mon, 27 Jan 2025 00:00:00 +0000</pubDate>
        <link>https://zverok.space/blog/2025-01-27-7things-euruko.html</link>
        <guid isPermaLink="true">https://zverok.space/blog/2025-01-27-7things-euruko.html</guid>
        
        
        <category>ruby</category>
        
        <category>rant</category>
        
      </item>
    
      <item>
        <title>The short outburst of activity during Ruby Changelog preparation—2025 edition</title>
        <description>&lt;p&gt;&lt;strong&gt;On making the Ruby Changes, and some consequences of this work&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every year since 2013, on December 25, a new Ruby version is released. (Before that, the version schedule was much unpredictable.)&lt;/p&gt;

&lt;p&gt;Every year since 2018 (Ruby 2.6), I spend several weeks in December working on updating my &lt;a href=&quot;https://rubyreferences.github.io/rubychanges/&quot;&gt;Ruby Changes&lt;/a&gt; site to prepare an annotated changelog of the language, focused on changes in syntax, semantics, and core APIs. You can see the 2024 installment, dedicated to Ruby 3.4, &lt;a href=&quot;https://rubyreferences.github.io/rubychanges/3.4.html&quot;&gt;here&lt;/a&gt;: 7k words, 30+ sections, and some ~60 hours of work.&lt;/p&gt;

&lt;p&gt;I have already written a series of articles about how and why I am doing it in 2022 (&lt;a href=&quot;https://zverok.space/blog/2022-01-06-changelog.html&quot;&gt;1&lt;/a&gt;, &lt;a href=&quot;https://zverok.space/blog/2022-01-13-it-evolves.html&quot;&gt;2&lt;/a&gt;, &lt;a href=&quot;https://zverok.space/blog/2022-01-20-still-flying.html&quot;&gt;3&lt;/a&gt;), along with some philosophical and personal implications of this work. Last year, I made it into an “&lt;a href=&quot;https://zverok.space/blog/2023-12-28-advent-of-changelog-fin.html&quot;&gt;advent&lt;/a&gt;” of following this work with explanations (which I myself called “a boring advent,” so I don’t think a lot of people have followed it).&lt;/p&gt;

&lt;p&gt;So, for this year, I am just doing a short wrap-up about the new chunk of work done.&lt;/p&gt;

&lt;h2 id=&quot;how-it-is-done&quot;&gt;How it is done&lt;/h2&gt;

&lt;p&gt;From the very inception of the “Ruby Changes” idea, my goals were to cover &lt;strong&gt;all&lt;/strong&gt; notable changes in every version (not only those I like or plan to use soon or those that are easy to describe), and to &lt;strong&gt;explain&lt;/strong&gt; them the way that would be educative and instructive. This means linking to the relevant documentation, providing the reasoning behind the change and how it was decided to be, and showing the code examples—both illustrating the change’s utility and demonstrating edge case behavior.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The “all changes” goal has a big sidenote in the case of my work. The “Ruby release” that happens each year, is a release of the language and its particular, authoritative implementation: CRuby; thus, NEWS-file of each new version includes a lot of changes that are related to how CRuby works, like parsing, garbage collection and so on. My area of interests and area of expertise is about the language as a tool of thinking/writing; so I only mention changes of the “insides” only to the extent they are manifested in changes of the API of the core classes. Some years it makes “Ruby Changes” focus significantly diverge from NEWS-file’s focus (and the focus of many blog posts that rely on it). This year, which is marked by the introduction of the new default parser, and of pluggable GC concept, is not an exception.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, every day of work during December, when I work on the changelog (it typically takes a few hours every day for a few weeks), I rebuild the most recent development version of Ruby and use it to write all examples to check how it all really works. I also read through every discussion on the official Ruby tracker (thankfully, the relevant issues are linked from each entry of the NEWS-file) and check the latest rendered docs at &lt;a href=&quot;https://docs.ruby-lang.org/en/master&quot;&gt;/master&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Every year, this activity, besides producing the changelog itself, allows spotting (and frequently fixing before the release) some deficiencies in documentation or even complicated edge cases on the new behavior. Here are some of this year’s examples.&lt;/p&gt;

&lt;h2 id=&quot;some-consequences&quot;&gt;Some consequences&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;A forewarning:&lt;/strong&gt; by no means I want to imply that something was “broken” in the Ruby development process that I “fixed.” The small amount of work that I am doing during my Decembers is just a fragment of what the modern mature language maintainers are dealing with. There are a lot of people who maintain the language, its implementation, tools, and documentation all year round. I just do my small part—and they say, in the modern world, you should talk about your work. So, there’s that.&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;documentation-improvements&quot;&gt;Documentation improvements&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/ruby/ruby/pull/12375&quot;&gt;Documenting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;it&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; (&lt;a href=&quot;https://rubyreferences.github.io/rubychanges/3.4.html#standalone-it-in-blocks-became-anonymous-argument&quot;&gt;changelog entry&lt;/a&gt;)—the new anonymous parameter lacked the documentation, so I wrote some (and rephrased the numbered block parameters documentation appropriately).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/ruby/ruby/pull/12228&quot;&gt;Documenting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;**nil&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; (&lt;a href=&quot;https://rubyreferences.github.io/rubychanges/3.4.html#nil-unpacks-into-empty-keyword-arguments&quot;&gt;changelog entry&lt;/a&gt;): While &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;**nil&lt;/code&gt; construct (allowing to unpack it into empty keyword arguments) is a relatively minor change; trying to find the place in docs for it, I found it necessary to rephrase the whole section of the documentation about parameter unpacking (both positional and keyword ones)—hopefully, for the better.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/ruby/ruby/pull/12420&quot;&gt;Document backtrace-related changes&lt;/a&gt;&lt;/strong&gt; (&lt;a href=&quot;https://rubyreferences.github.io/rubychanges/3.4.html#exception-backtrace_locations-can-be-set-programmatically&quot;&gt;changelog entry&lt;/a&gt;): Structured caller/error backtrace values (in the form of &lt;a href=&quot;https://docs.ruby-lang.org/en/3.4/Thread/Backtrace/Location.html&quot;&gt;Thread::Backtrace::Location&lt;/a&gt;) were introduced in Ruby 2.1, but only since 3.4 the structured backtrace can be set by a developer (when raising or post-processing the error). Integrating the concept properly with the documentation required some perspective changes in several places.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;behavior-clarifications&quot;&gt;Behavior clarifications&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://rubyreferences.github.io/rubychanges/3.4.html#standalone-it-in-blocks-became-anonymous-argument&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;it&lt;/code&gt; anonymous block parameter&lt;/a&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;There are nuances related to the introspection of the parameters of the Proc (reported &lt;a href=&quot;https://bugs.ruby-lang.org/issues/20955&quot;&gt;here&lt;/a&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;c1&quot;&gt;# Old numbered arguments are introspected this way:&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;proc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parameters&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [[:opt, :_1]]&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parameters&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [[:req, :_1]]&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# But new `it` is reported differently,&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# and inconsistently between proc and lambda:&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;proc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parameters&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [[:opt, nil]]&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parameters&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [[:req]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The local variables introspection for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;it&lt;/code&gt;-based blocks also behave differently from numbered parameters (reported &lt;a href=&quot;https://bugs.ruby-lang.org/issues/20965&quot;&gt;here&lt;/a&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;nb&quot;&gt;proc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;_1&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;binding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;local_variables&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [:_1]&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;proc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;binding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;local_variables&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [] -- `it` is not mentioned&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once I noticed the pair of problems, there was even an &lt;a href=&quot;https://bugs.ruby-lang.org/issues/20965#note-3&quot;&gt;attempt&lt;/a&gt; to fix it. However, it turned out not that easy to do without introducing new inconsistencies/problems, so the “last-minute fix” was &lt;a href=&quot;https://bugs.ruby-lang.org/issues/20965#note-7&quot;&gt;reverted&lt;/a&gt;. The desirable behavior will be discussed during the work on Ruby 3.5. Most probably, such inconsistencies wouldn’t affect the production code, but eventually, they are to be fixed for teaching/understanding the language value, if nothing else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://rubyreferences.github.io/rubychanges/3.4.html#arrayfetch_values&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Array#fetch_values&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The new method to fetch several values at once or raise/use defaults when they are absent (like &lt;a href=&quot;https://docs.ruby-lang.org/en/3.4/Hash.html#method-i-fetch_values&quot;&gt;Hash#fetch_value&lt;/a&gt;) was introduced. However, its accepted argument types are inconsistent with &lt;a href=&quot;https://docs.ruby-lang.org/en/3.4/Array.html#method-i-values_at&quot;&gt;Array#values_at&lt;/a&gt; (the method that does the same, but without the control for absence):&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;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;p&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;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;values_at&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;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;o&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;c1&quot;&gt;#=&amp;gt; [1, 4, 5]&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;p&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;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fetch_values&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;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;o&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;c1&quot;&gt;# TypeError: in &apos;Array#fetch&apos;: no implicit conversion of Range into Integer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://bugs.ruby-lang.org/issues/20953&quot;&gt;The discussion&lt;/a&gt; about proper behavior wasn’t finished in time for the 3.4.0 release (because it was reported late), so the decision will probably be made in the upcoming year.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://rubyreferences.github.io/rubychanges/3.4.html#size-raises-typeerror-if-the-range-is-not-iterable&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Range#size&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; has a new behavior to throw &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TypeError&lt;/code&gt; when the Range of a non-iterable type, but it has a small &lt;a href=&quot;https://bugs.ruby-lang.org/issues/20980&quot;&gt;edge case&lt;/a&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;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;size&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# 3.3: nil&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# 3.4: can&apos;t iterate from Time (TypeError) -- new behavior&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# But for beginless numeric ranges, there is a catch:&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;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;size&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# 3.3: Infinity -- special handling, might be useful&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# 3.4: can&apos;t iterate from NilClass (TypeError) -- is it worse?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Again, I hope to discuss this edge case during the work on Ruby 3.5.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;A small funny thing that happened during producing the changelog examples is that I noticed that the (new) &lt;a href=&quot;https://rubyreferences.github.io/rubychanges/3.4.html#-and-&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ractor::[]&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;::[]=&lt;/code&gt; methods&lt;/a&gt; (Ractor-local storage) accepts both String and Symbol keys, like (existing) &lt;a href=&quot;https://docs.ruby-lang.org/en/3.4/Thread.html#method-i-5B-5D&quot;&gt;Thread#[]&lt;/a&gt;/&lt;a href=&quot;https://docs.ruby-lang.org/en/3.4/Thread.html#method-i-5B-5D-3D&quot;&gt;#[]=&lt;/a&gt; but &lt;em&gt;unlike&lt;/em&gt; (introduced in &lt;a href=&quot;https://rubyreferences.github.io/rubychanges/3.2.html#fiber-storage&quot;&gt;3.2&lt;/a&gt;) &lt;a href=&quot;https://docs.ruby-lang.org/en/3.4/Fiber.html#method-c-5B-5D&quot;&gt;Fiber::[]&lt;/a&gt;/&lt;a href=&quot;https://docs.ruby-lang.org/en/3.4/Fiber.html#method-c-5B-5D-3D&quot;&gt;::[]=&lt;/a&gt;, which only accepted Symbols. After &lt;a href=&quot;https://bugs.ruby-lang.org/issues/20978&quot;&gt;reporting&lt;/a&gt;, the Fiber behavior was considered a bug and fixed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;one-ukrainian-thing&quot;&gt;
  &lt;h3&gt;A postcard from 🇺🇦&lt;/h3&gt;

  &lt;p&gt;&lt;em&gt;&lt;strong&gt;Please stop here for a moment.&lt;/strong&gt; This is your regular mid-text reminder that I am a living person from Ukraine, with the Russian invasion still ongoing. Please read it.&lt;/em&gt;&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;One news item.&lt;/strong&gt; &lt;a href=&quot;https://x.com/KramarenkoMari3/status/1875492311599100089&quot;&gt;In the first three days of the year, the russians killed three Ukrainian scientists: Ihor Zyma, a doctor of biological sciences and Olesya Sokur, a doctor of biological sciences, his wife, in Kyiv; Oleksiy Galyonka, a Candidate of Pedagogical Sciences (PhD) in Chernihiv.&lt;/a&gt;&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;One piece of context.&lt;/strong&gt; &lt;a href=&quot;https://x.com/nastasiaKlimash/status/1875470857453416932&quot;&gt;Russians promote [Ukrainian] Christmas carols in Moskva as a “Russian cultural tradition, while at the same time banning Christmas caroling on the territories of Ukraine they’ve occupied&lt;/a&gt;.&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;One report.&lt;/strong&gt; Stanislav Aseyev is a Ukrainian writer, journalist, veteran, and survivor of the Izolyatsia prison in Russia-occupied Donetsk, infamous for its torture of prisoners. He was the first Ukrainian journalist to see the Sednaya prison and death camp in Syria after the fall of Bashar al-Assad’s regime in 2024. &lt;a href=&quot;https://kyivindependent.com/sednaya-bashar-al-assads-death-camp/&quot;&gt;Read his report.&lt;/a&gt;&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;One fundraiser.&lt;/strong&gt; Please help &lt;a href=&quot;https://x.com/serhii_rieznik/status/1872570550410723729&quot;&gt;this urgent fundraiser&lt;/a&gt; for a robotic ground systems unit.&lt;/p&gt;
&lt;/div&gt;

&lt;h2 id=&quot;my-favorite-overlooked-feature-this-year&quot;&gt;My favorite overlooked feature this year&lt;/h2&gt;

&lt;p&gt;One of the reasons I started to do this work back in 2018 was an uneasy feeling of “(almost) everybody missing an important point.” Every year, many posts are published by various authors dedicated to explaining what’s new in Ruby. Almost every year, there is some change in syntax or API most of these posts overlook, probably considering it “some boring technical detail,” while for me, it feels like quality-of-life improvement stuff.&lt;/p&gt;

&lt;p&gt;This year, such a feature was one developed by yours truly, so I apologize in advance for the shameless plug. It is a change in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Range#step&lt;/code&gt; behavior with non-numeric ranges, which makes range iteration much more powerful.&lt;/p&gt;

&lt;p&gt;As I explained in one of the recent articles, there was a long-standing historical inconsistency in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Range#step&lt;/code&gt; iteration: with numbers, the argument was treated as “an amount to add on each step,” while for all other types, it meant “number of times to make a step forward with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#succ&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;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&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;nf&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_a&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# uses +&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [1.0, 1.5, 2.0, 2.5, 3.0]&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;active_support/all&apos;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&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;nf&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;minutes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;take&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;c1&quot;&gt;# tries to use #succ&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# TypeError (can&apos;t iterate from Time)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So the only usage of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#step&lt;/code&gt; with non-numeric types was “several steps at a time”:&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;s1&quot;&gt;&apos;a&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;e&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;step&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;nf&quot;&gt;to_a&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [&quot;a&quot;, &quot;c&quot;, &quot;e&quot;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Since Ruby 3.4, ranges of any type can use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#step&lt;/code&gt; in a way that feels natural:&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;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&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;nf&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;minutes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;take&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;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [2025-01-04 21:39:44.369461267 +0200, 2025-01-04 22:09:44.369461267 +0200, 2025-01-04 22:39:44.369461267 +0200]&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;unitwise&apos;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Unitwise&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;ss&quot;&gt;:km&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;no&quot;&gt;Unitwise&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;ss&quot;&gt;:km&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Unitwise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_a&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# =&amp;gt; [#&amp;lt;Unitwise::Measurement value=1 unit=km&amp;gt;, #&amp;lt;Unitwise::Measurement value=1.5 unit=km&amp;gt;, #&amp;lt;Unitwise::Measurement value=2 unit=km&amp;gt;]&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;matrix&apos;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Vector&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;1&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;nf&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Vector&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;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;take&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;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [Vector[1, 1], Vector[2, 2], Vector[3, 3]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I expect time iteration to be the most widespread usage of the new feature, but I like it for introducing more power/consistency into the language.&lt;/p&gt;

&lt;h2 id=&quot;future-possibilities&quot;&gt;Future possibilities&lt;/h2&gt;

&lt;p&gt;For a couple of years now, I promised myself to start maintaining the changelog “alive,” having the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt; section during the year (the same way Ruby documentation has the &lt;a href=&quot;https://docs.ruby-lang.org/en/master&quot;&gt;master&lt;/a&gt; branch version always rendered).&lt;/p&gt;

&lt;p&gt;I hope this approach might allow to make people acquainted with what’s coming; it will also allow my “side effect” activities of changelog preparation to be done in a timely manner—which is especially important for confirming the edge cases like some with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;it&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Array#fetch_values&lt;/code&gt; discussed above. Also, it would allow me to notice some other necessary documentation improvements. I frequently notice some things worth improving in existing docs (the methods neighboring or similar to the changed ones) but usually put them in my “long TODO” list… To never act upon, once the rush of finalizing this year’s release is over.&lt;/p&gt;

&lt;p&gt;Maybe this year will be the one when I’ll finally act on this intention. Maybe not. We’ll see.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;Thank you for reading. Please support Ukraine with your donations and lobbying for military and humanitarian help. &lt;a href=&quot;https://war.ukraine.ua/&quot;&gt;Here&lt;/a&gt;, you’ll find a comprehensive information source and many links to state and private funds accepting donations.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you don’t have time to process it all, donating to &lt;a href=&quot;https://savelife.in.ua/en/&quot;&gt;Come Back Alive&lt;/a&gt; foundation is always a good choice.&lt;/strong&gt;&lt;/p&gt;

&lt;iframe src=&quot;https://zverok.substack.com/embed&quot; width=&quot;480&quot; height=&quot;320&quot; style=&quot;border:1px solid #EEE; background:white;&quot; frameborder=&quot;0&quot; scrolling=&quot;no&quot;&gt;&lt;/iframe&gt;
</description>
        <pubDate>Mon, 06 Jan 2025 00:00:00 +0000</pubDate>
        <link>https://zverok.space/blog/2025-01-06-changelog-2025.html</link>
        <guid isPermaLink="true">https://zverok.space/blog/2025-01-06-changelog-2025.html</guid>
        
        
        <category>ruby</category>
        
      </item>
    
      <item>
        <title>Elixir-like pipes in Ruby (oh no not again)</title>
        <description>&lt;p&gt;&lt;strong&gt;On a new approach to implement that long-envied feature.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In Elixir&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;, there is a &lt;a href=&quot;https://hexdocs.pm/elixir/main/Kernel.html#%7C%3E/2&quot;&gt;pipeline operator&lt;/a&gt; that allows rewriting this code:&lt;/p&gt;

&lt;div class=&quot;language-elixir 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;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d&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;into this:&lt;/p&gt;
&lt;div class=&quot;language-elixir 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;d&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&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;which helps to write code in clearly visible “pipelines” corresponding to the order in which data processing is happening.&lt;/p&gt;

&lt;p&gt;The concept is so captivating that many languages that don’t have an operator like this have either &lt;a href=&quot;https://github.com/tc39/proposal-pipeline-operator&quot;&gt;language proposals&lt;/a&gt; or libraries that implement one. Ruby is no exception.&lt;/p&gt;

&lt;p&gt;This is a story of my take on implementing it.&lt;/p&gt;

&lt;p&gt;But first…&lt;/p&gt;

&lt;h2 id=&quot;why-we-dont-need-it&quot;&gt;Why we don’t need it&lt;/h2&gt;

&lt;p&gt;There have been many proposals to introduce the operator to Ruby over the years. The crucial reason they are typically met with skepticism is that Ruby’s API is very different from Elixir’s. In Elixir, most of the methods belong to modules, while in Ruby, they belong to objects we are processing. So the usual Elixir’s &lt;a href=&quot;https://exercism.org/tracks/elixir/concepts/pipe-operator&quot;&gt;motivating examples&lt;/a&gt; like&lt;/p&gt;
&lt;div class=&quot;language-elixir 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;s2&quot;&gt;&quot;go &quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;|&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;duplicate&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;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;|&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;upcase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;|&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;replace_suffix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;!&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;…wouldn’t require any additional handling in Ruby to make it flow in the right direction:&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;s2&quot;&gt;&quot;go &quot;&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;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;upcase&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/ $/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;!&apos;&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;Even most operators can be written in method-chaining style, so we also can do this (though not to everybody’s liking):&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;s2&quot;&gt;&quot;go &quot;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;*&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;p&quot;&gt;)&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;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/ $/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;!&apos;&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;When the method we want to call doesn’t belong to the last object in the chain, we have an escape hatch of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.then&lt;/code&gt; (introduced in &lt;a href=&quot;https://rubyreferences.github.io/rubychanges/2.5.html#yield_self&quot;&gt;Ruby 2.5&lt;/a&gt; as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yield_self&lt;/code&gt;, and renamed to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;then&lt;/code&gt; in &lt;a href=&quot;https://rubyreferences.github.io/rubychanges/2.6.html#then-as-an-alias-for-yield_self&quot;&gt;Ruby 2.6&lt;/a&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;s2&quot;&gt;&quot;https://api.github.com/users/&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/repos&quot;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;URI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_1&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;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;then&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;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;symbolize_names: &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;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:full_name&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;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&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;nf&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pp&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_1&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;There was even &lt;a href=&quot;https://bugs.ruby-lang.org/issues/15799&quot;&gt;an attempt&lt;/a&gt; to introduce something looking like a “pipeline operator” in Ruby, giving up to the pressure to have it but making it just another way of writing the classic chains. I.e. the example with strings above might’ve been rewritten:&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;s2&quot;&gt;&quot;go &quot;&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;o&quot;&gt;|&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;upcase&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sub&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;!&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The idea was backed by Matz and even merged for a short period before Ruby 2.7, but it caused so much distress and confusion (for bringing the new syntax, which didn’t &lt;em&gt;do&lt;/em&gt; anything new) that it was &lt;a href=&quot;https://bugs.ruby-lang.org/issues/15799#note-46&quot;&gt;reverted&lt;/a&gt; even before the release.&lt;/p&gt;

&lt;p&gt;Another reason why introducing something like a pipeline operator in Ruby is problematic is that &lt;strong&gt;we don’t have first-class method references&lt;/strong&gt;. Due to how Ruby methods &lt;a href=&quot;https://zverok.space/blog/2024-06-14-method-evolution.html&quot;&gt;are designed&lt;/a&gt;, something like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URI.open&lt;/code&gt; is &lt;em&gt;not&lt;/em&gt; a reference to a method object but an immediate call, and the only way to refer to the method is to do &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URI.method(:open)&lt;/code&gt;—which, to the best of my understanding, is not only wordy but also is not “cheap” because it would create an OO-representation of a method, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Method&lt;/code&gt; object, on the call. So, trying to optimize the “parse from URL” example above with a pipeline operator will require to put a reference to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URI.open&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JSON.parse&lt;/code&gt; into it, and there is no nice way to do that.&lt;/p&gt;

&lt;p&gt;And still…&lt;/p&gt;

&lt;h2 id=&quot;why-i-did-it-anyway&quot;&gt;Why I did it anyway&lt;/h2&gt;

&lt;p&gt;My experiment &lt;strong&gt;is not a proposal as a library or as a core feature&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It was inspired by yet another &lt;a href=&quot;https://bugs.ruby-lang.org/issues/20770&quot;&gt;discussion&lt;/a&gt; about introducing a pipe operator in Ruby. Initially, it started as they all go (with pipe operator being some syntax sugar on top of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.then { code }&lt;/code&gt;). After some arguing, though, it took an &lt;a href=&quot;https://bugs.ruby-lang.org/issues/20770#note-34&quot;&gt;interesting turn&lt;/a&gt;, where Alexander Magro, the submitter of the initial ticket, proposed this:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;What I (re)propose is to define the pipe operator as a statement separator, similar to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;;&lt;/code&gt; […]&lt;/p&gt;

  &lt;p&gt;This way, we could write:&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;s2&quot;&gt;&quot;https://api.github.com/repos/ruby/ruby&quot;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;|&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;URI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;|&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Net&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;HTTP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;|&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;|&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;stargazers_count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;|&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Ruby has &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; stars&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
&lt;/blockquote&gt;

&lt;p&gt;I still doubt this proposal has a chance to make it into the language core, but there is at least some fresh take here and an interesting justification.&lt;/p&gt;

&lt;p&gt;Another piece of the puzzle that finally &lt;a href=&quot;https://xkcd.com/356/&quot;&gt;nerd-sniped&lt;/a&gt; me was recently released Python’s &lt;a href=&quot;https://github.com/Jordan-Kowal/pipe-operator&quot;&gt;pipe_operator&lt;/a&gt; library (like in Ruby, there were many attempts through the years). It provides two alternative implementations, one of them is regular “just a DSL”:&lt;/p&gt;

&lt;div class=&quot;language-py 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;PipeStart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;3&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;                        &lt;span class=&quot;c1&quot;&gt;# starts the pipe
&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;                          &lt;span class=&quot;c1&quot;&gt;# function with 1-arg
&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;my_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;          &lt;span class=&quot;c1&quot;&gt;# function with multiple args
&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Tap&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;)&lt;/span&gt;                         &lt;span class=&quot;c1&quot;&gt;# side effect
&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;lambda&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;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&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;c1&quot;&gt;# lambda
# ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;but the other one has tickled my curiosity because I couldn’t, from the top of my head, guess how it is implemented:&lt;/p&gt;

&lt;div class=&quot;language-py 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;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;pipe_operator&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;elixir_pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;then&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;elixir_pipe&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;workflow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;results&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;value&lt;/span&gt;                           &lt;span class=&quot;c1&quot;&gt;# raw value
&lt;/span&gt;        &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BasicClass&lt;/span&gt;                   &lt;span class=&quot;c1&quot;&gt;# class call
&lt;/span&gt;        &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;                      &lt;span class=&quot;c1&quot;&gt;# property (shortcut)
&lt;/span&gt;        &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BasicClass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;                 &lt;span class=&quot;c1&quot;&gt;# class call
&lt;/span&gt;        &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_value_plus_arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;     &lt;span class=&quot;c1&quot;&gt;# method call
&lt;/span&gt;        &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;                   &lt;span class=&quot;c1&quot;&gt;# binary operation (shortcut)
&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;n&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;p&quot;&gt;}&lt;/span&gt;                 &lt;span class=&quot;c1&quot;&gt;# object creation (shortcut)
&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;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;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;c1&quot;&gt;# comprehension (shortcut)
&lt;/span&gt;        &lt;span class=&quot;c1&quot;&gt;# ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As it turned out, the approach that makes it work is &lt;a href=&quot;https://github.com/Jordan-Kowal/pipe-operator?tab=readme-ov-file#-elixir-like-implementation&quot;&gt;an interesting one&lt;/a&gt;: when the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@elixir_pipe&lt;/code&gt; decorator is applied to a method, it &lt;em&gt;transforms the method’s AST&lt;/em&gt; and defines a completely different method, where the code is rewritten in a more traditional way. Basically, it is full-fledged load-time syntactic macros. Curious.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That’s what I immediately wanted to try to reproduce!&lt;/strong&gt; Not the pipe operator (which is just a pretext for the experiment), but this “local rewriting” of methods using their syntax tree. I am not sure I ever saw such an approach used in Ruby, though I might miss something (the &lt;a href=&quot;https://github.com/ruby-next/ruby-next&quot;&gt;ruby-next&lt;/a&gt; transpiler does code transformation at load time, but on the level of the whole file, which seems quite a different thing; “transpilers” and “inline syntactic macros” are different by perception and usage).&lt;/p&gt;

&lt;p&gt;And, lo and behold, &lt;a href=&quot;https://github.com/zverok/not_a_pipe&quot;&gt;here we are&lt;/a&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;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;not_a_pipe&apos;&lt;/span&gt;

&lt;span class=&quot;kp&quot;&gt;extend&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NotAPipe&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;pipe&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;repos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;https://api.github.com/users/&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/repos&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;URI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;symbolize_names: &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;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:full_name&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;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;pp&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;repos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;zverok&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# prints: [&quot;zverok/any_good&quot;, &quot;zverok/awesome-codebases&quot;, &quot;zverok/awesome-events&quot;, &quot;zverok/backports&quot;, ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Basically:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pipe&lt;/code&gt; is a &lt;em&gt;decorator&lt;/em&gt;&lt;sup id=&quot;fnref:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; to mark methods inside which &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;gt;&amp;gt;&lt;/code&gt; works as a “pipe operator”;&lt;/li&gt;
  &lt;li&gt;every step can reference &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_&lt;/code&gt; which would be a result of the previous step;&lt;/li&gt;
  &lt;li&gt;but it also can &lt;strong&gt;omit the reference&lt;/strong&gt; and just specify a method to call; the result of the previous step would be substituted as the &lt;em&gt;first argument&lt;/em&gt; of the method.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It works. It is &lt;a href=&quot;https://github.com/zverok/not_a_pipe/blob/main/lib/not_a_pipe.rb&quot;&gt;small&lt;/a&gt; and doesn’t introduce any monkey-patches to core objects. It is &lt;a href=&quot;https://github.com/zverok/not_a_pipe?tab=readme-ov-file#benchmarks&quot;&gt;fast&lt;/a&gt;, too, unlike typical situation of adding some new expressiveness DSL slapped on top of Ruby.&lt;/p&gt;

&lt;p&gt;It is also, probably, an awful idea.&lt;/p&gt;

&lt;div class=&quot;one-ukrainian-thing&quot;&gt;
  &lt;h3&gt;A postcard from 🇺🇦&lt;/h3&gt;

  &lt;p&gt;&lt;em&gt;&lt;strong&gt;Please stop here for a moment.&lt;/strong&gt; This is your regular mid-text reminder that I am a living person from Ukraine, with the Russian invasion still ongoing. Please read it.&lt;/em&gt;&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;One news item.&lt;/strong&gt; &lt;a href=&quot;https://x.com/khpg/status/1857466568453537800&quot;&gt;Russia sentences Ukrainian mother of four to 14 years on ‘treason’ charges for supporting Ukraine.&lt;/a&gt;&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;One piece of context.&lt;/strong&gt; “A reminder that the 1st Chechen War started by Russia ended not in RU’s victory.” Read &lt;a href=&quot;https://x.com/nastasiaKlimash/status/1621960987371773952&quot;&gt;this thread&lt;/a&gt; that gives a grim picture of how the peace treaties with Russia usually go.&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;One fundraiser.&lt;/strong&gt; Please help &lt;a href=&quot;https://x.com/KonstantinTeam/status/1852700048615784780&quot;&gt;boost this fundraiser&lt;/a&gt; from always reliable and transparent Project Konstantin.&lt;/p&gt;
&lt;/div&gt;

&lt;h2 id=&quot;how-it-works&quot;&gt;How it works&lt;/h2&gt;

&lt;p&gt;The reference to the Python library that inspired me already gave up the trick&lt;/p&gt;

&lt;p&gt;The code above is syntactically valid Ruby. But it is Ruby code that can’t be made to work with some small library/metaprogramming additions: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URI.open&lt;/code&gt; is a method that requires at least one argument, as well as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JSON.parse&lt;/code&gt;, and there is no way to convert them to some kind of “deferred method references”&lt;sup id=&quot;fnref:3&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;. Also, no tricks will allow to introduce &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_&lt;/code&gt; local variable at the middle of the pipe without it being explicitly defined (it can be made some contextual method, probably).&lt;/p&gt;

&lt;p&gt;But this is not a problem, as this code is &lt;strong&gt;never executed&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pipe&lt;/code&gt; “decorator,” when applied to a method, does the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;loads the method’s source;&lt;/li&gt;
  &lt;li&gt;parses it with &lt;a href=&quot;https://github.com/whitequark/parser&quot;&gt;parser&lt;/a&gt; gem into AST (abstract syntax tree);&lt;/li&gt;
  &lt;li&gt;transforms the AST’s shape into different AST that would work as the pipe expected to work;&lt;/li&gt;
  &lt;li&gt;converts the new AST into Ruby code with &lt;a href=&quot;https://github.com/mbj/unparser&quot;&gt;unparser&lt;/a&gt;;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eval&lt;/code&gt;s the new code in the target class, basically redefining the method.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The code of the method that is really becomes defined is roughly 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;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;repos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;https://api.github.com/users/&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/repos&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;URI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;symbolize_names: &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;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:full_name&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;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&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;blockquote&gt;
  &lt;p&gt;As an aside note, I would’ve be happy if this parsing/transformations could be performed by Ruby’s standard library, but I am not aware of any “unparser” (AST-to-source code) transformation solution based on either Ruby::AST or Prism. So, that’s that. I am also not aware of any standard method of obtaining the method’s source code other than “take location and look in file” which &lt;a href=&quot;https://github.com/banister/method_source&quot;&gt;method_source&lt;/a&gt; gem implements. Would be cool to have some standard introspection tools in core/standard library, too.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The transformation code (even as written as proof-of-concept/demo) is robust enough to handle the usage of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;gt;&amp;gt;&lt;/code&gt; pipelines in the middle of a longer method, not only “the whole method should be a pipeline.” So this would work, too:&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;pipe&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;repos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;repos&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;https://api.github.com/users/&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/repos&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;URI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;symbolize_names: &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;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:full_name&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;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&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;the rest of the code (doing something with &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;repos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; repos)&quot;&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Anything can be here, including other &amp;gt;&amp;gt; pipelines&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;…or, with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;=&amp;gt;&lt;/code&gt; right-hand assignment (that is brought by pattern-matching), 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;pipe&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;repos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;https://api.github.com/users/&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/repos&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;URI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;symbolize_names: &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;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:full_name&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;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;repos&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# ...the rest of the method, `repos` contains the result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;…which seems to combine various flow operators handsomely!&lt;/p&gt;

&lt;h2 id=&quot;some-conclusions&quot;&gt;Some conclusions&lt;/h2&gt;

&lt;p&gt;Once again, I am not saying that this experiment is dedicated to the introduction of a new operator in Ruby. The “library” is rough, experimental, sarcastically named, and will probably stay this way.&lt;/p&gt;

&lt;p&gt;What I tried to do here is to &lt;strong&gt;experiment with the approach&lt;/strong&gt; that would give macros-like capabilities without runtime overhead and deep invasion into core classes.&lt;/p&gt;

&lt;p&gt;When might this approach be useful?&lt;/p&gt;

&lt;p&gt;First, in cases like this: in a “laboratory” of the new language features. Would I be more sympathetic to the idea of this operator in general, I would try to work in some hobby project for some time with this addition to gather more data about whether it is really frequently useful and might deserve a place in the language.&lt;/p&gt;

&lt;p&gt;Another possible usage is a &lt;em&gt;strictly limited&lt;/em&gt; application: not something that is used throughout the entire codebase, but a thin/low-overhead implementation of a domain-specific language (emphasizing the &lt;strong&gt;domain&lt;/strong&gt;-specific here) like Rake, or Sinatra/Grape, or Arel, or description of some ML/data-processing algorithm in a clear and concise manner.&lt;/p&gt;

&lt;p&gt;Also, such an approach might be used for &lt;em&gt;small&lt;/em&gt; rewrites of the method code (though I am not sure that possible gains are worth the added complexity), like, say, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;safe_sql def my_query&lt;/code&gt; that wraps all literal strings in a method into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Arel.sql&lt;/code&gt; to make the overall code clearer.&lt;/p&gt;

&lt;p&gt;BTW, would I want to make &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;not_a_pipe&lt;/code&gt; a (somewhat) production-ready library, I would also&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;work through “unhappy paths,” from invalid syntax in the method to some parts of the pipeline throwing an error (we need to make sure that the backtrace would show the accurate file/line);&lt;/li&gt;
  &lt;li&gt;perhaps introduce an argument-less form of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pipe&lt;/code&gt; method to handle &lt;em&gt;all&lt;/em&gt; methods below the call (like argument-less &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Module#private&lt;/code&gt;), by catching &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;method_added&lt;/code&gt; hook.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some aside conclusions from this exercise are that:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;it would be cool to see now, that Prism becomes Ruby’s default parser, its further development into something that would allow AST rewriting and code introspection;&lt;/li&gt;
  &lt;li&gt;I personally find “decorators” (the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;something def my_method&lt;/code&gt;) underused in modern Ruby practices, and I would like to see them more used; even if not as powerful as Python’s decorators, they allow for some interesting&lt;/li&gt;
  &lt;li&gt;Structural pattern-matching is stunningly convenient for handling AST transformations;&lt;/li&gt;
  &lt;li&gt;This was a fun way to spend half a Saturday, after all!&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;Thank you for reading. Please support Ukraine with your donations and lobbying for military and humanitarian help. &lt;a href=&quot;https://war.ukraine.ua/&quot;&gt;Here&lt;/a&gt;, you’ll find a comprehensive information source and many links to state and private funds accepting donations.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you don’t have time to process it all, donating to &lt;a href=&quot;https://savelife.in.ua/en/&quot;&gt;Come Back Alive&lt;/a&gt; foundation is always a good choice.&lt;/strong&gt;&lt;/p&gt;

&lt;iframe src=&quot;https://zverok.substack.com/embed&quot; width=&quot;480&quot; height=&quot;320&quot; style=&quot;border:1px solid #EEE; background:white;&quot; frameborder=&quot;0&quot; scrolling=&quot;no&quot;&gt;&lt;/iframe&gt;
&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;…and in several other languages, but Elixir’s is the most widely-known one… except for shells, of course, which probably have originated the concept. But in many cases, when describing modern mainstream languages, the feature is referred to as “Elixir(-like) pipe operator.” &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;In truth, Ruby does not have a separate “decorator” concept. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pipe&lt;/code&gt; here is just a module-level method that receives the result of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;def repos&lt;/code&gt; (which is the method’s name, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:repos&lt;/code&gt;), and then uses this name to somehow process the method. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://github.com/LendingHome/pipe_operator&quot;&gt;This library&lt;/a&gt; &lt;em&gt;has found&lt;/em&gt; a way to do so by introducing a whole world of “proxy objects” inside the pipe context. &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Sat, 16 Nov 2024 00:00:00 +0000</pubDate>
        <link>https://zverok.space/blog/2024-11-16-elixir-pipes.html</link>
        <guid isPermaLink="true">https://zverok.space/blog/2024-11-16-elixir-pipes.html</guid>
        
        
        <category>ruby</category>
        
      </item>
    
      <item>
        <title>There is no such thing as a global method (in Ruby)</title>
        <description>&lt;p&gt;&lt;strong&gt;What Ruby’s top-level methods actually are, who they belong to and how they are namespaced.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A few days ago, a curious question &lt;a href=&quot;https://www.reddit.com/r/ruby/comments/1g5ma34/question_about_kernelrand/&quot;&gt;was asked&lt;/a&gt; on /r/ruby, which can be boiled down to this: &lt;strong&gt;How are the methods of the &lt;a href=&quot;https://docs.ruby-lang.org/en/3.3/Kernel.html&quot;&gt;Kernel&lt;/a&gt; module available in the top-level scope?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The question was dedicated to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rand&lt;/code&gt; method, but (as the author correctly suggests) it also applies to many seemingly “top-level” methods documented as belonging to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Kernel&lt;/code&gt; module, even as base as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;puts&lt;/code&gt; (print a string), &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require&lt;/code&gt; (load code from another file), or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;raise&lt;/code&gt; (an exception).&lt;/p&gt;

&lt;p&gt;We know that in Ruby, all methods belong to some objects and are defined in their classes or modules. The documentation suggests that all of those “global” methods are coming from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Kernel&lt;/code&gt; module, yet you typically call them without referring to any module, object, or any other ceremonies (like loading some namespace or adding it to the current scope). So, &lt;strong&gt;how to understand this working?&lt;/strong&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;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Hello World&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;it-is-always-a-method-of-self&quot;&gt;It is always a method of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;In Ruby (unlike most other OO languages), the bare lowercase identifier &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;foo&lt;/code&gt; always refers to either a local variable or (if there is no such variable in the current scope) &lt;em&gt;the method of the current object&lt;/em&gt; (the one that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt; refers to in the current scope).&lt;/p&gt;

&lt;p&gt;So,&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;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Hello world&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;is always&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; the same as&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;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Hello world&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt; in the top-level scope? It is a special object, which is called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt; (though you can’t access it by this identifier, so “main” is just a representation thing):&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;nb&quot;&gt;self&lt;/span&gt;                 &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; main&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;class&lt;/span&gt;           &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; Object&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;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;span class=&quot;c1&quot;&gt;#=&amp;gt; [Object, Kernel, BasicObject]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So, we can see that it is just an instance of the generic &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Object&lt;/code&gt;, available as a top-level scope, and as such, it includes the module &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Kernel&lt;/code&gt; and all methods from it, therefore making &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;puts&lt;/code&gt; available:&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;m&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:puts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; #&amp;lt;Method: Object(Kernel)#puts(*)&amp;gt;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;owner&lt;/span&gt;            &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; Kernel -- who defined it&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;receiver&lt;/span&gt;         &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; main -- object which will receive the call&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But… What’s the deal with this &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Kernel&lt;/code&gt; module anyway?&lt;/p&gt;

&lt;h3 id=&quot;confusing-legacy-quirk-kernel-vs-object&quot;&gt;Confusing legacy quirk: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Kernel&lt;/code&gt; vs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Object&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Ideologically&lt;/em&gt;, it was intended for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Kernel&lt;/code&gt; module to be a storage for those “global” methods, available everywhere. All of them are private&lt;sup id=&quot;fnref:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;, i.e., available only to call on the current object from inside this object, thus making them look global:&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;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;private_methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:puts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; true&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ref&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Something&quot;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# NoMethodError: private method `puts&apos; called for main:Object&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;At the same time, methods defined in a base &lt;a href=&quot;https://docs.ruby-lang.org/en/3.3/Object.html&quot;&gt;Object&lt;/a&gt; class, the common ancestor of all other classes&lt;sup id=&quot;fnref:3&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;, are &lt;em&gt;public&lt;/em&gt; methods that are available on every object for other objects, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#inspect&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#to_s&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#respond_to?(method)&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#is_a?(some_class)&lt;/code&gt;, and so on.&lt;/p&gt;

&lt;p&gt;But that’s how it was &lt;em&gt;meant to be&lt;/em&gt;. In fact, &lt;em&gt;most&lt;/em&gt; of the above public methods are also defined in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Kernel&lt;/code&gt;, which is easy to check:&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;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;instance_method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:is_a?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;       &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; #&amp;lt;UnboundMethod: Kernel#is_a?(_)&amp;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;nf&quot;&gt;instance_method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:is_a?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;owner&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; Kernel&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;nf&quot;&gt;instance_methods&lt;/span&gt; &lt;span class=&quot;o&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;nf&quot;&gt;instance_methods&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [:!, :equal?, :__id__, :__send__, :==, :!=, :instance_eval, :instance_exec]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;As you can see, a very small batch of what is “ideologically” public methods of every object are actually defined in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Object&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But&lt;/strong&gt;, looking at the &lt;a href=&quot;https://docs.ruby-lang.org/en/3.3/Object.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Object&lt;/code&gt;’s docs&lt;/a&gt;, you’ll see much more of them. This is literally a hack in RDoc (Ruby’s documentation system) to make it &lt;em&gt;look&lt;/em&gt; like it is meant to be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But&lt;/strong&gt;, this hack is old and unaware of core methods being defined with Ruby (not C) code, and some of the public (ideologically &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Object&lt;/code&gt;’s) methods “slip” back into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Kernel&lt;/code&gt;, like &lt;a href=&quot;https://docs.ruby-lang.org/en/3.3/Kernel.html#method-i-then&quot;&gt;#then&lt;/a&gt; or even &lt;a href=&quot;https://docs.ruby-lang.org/en/3.3/Kernel.html#method-i-class&quot;&gt;#class&lt;/a&gt; (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;obj.class&lt;/code&gt;). There is a &lt;a href=&quot;https://bugs.ruby-lang.org/issues/19304&quot;&gt;long discussion&lt;/a&gt; about handling it in a saner way (which includes some explanation for how it happened), but it hasn’t moved much yet.&lt;/p&gt;

&lt;h2 id=&quot;when-you-puts-inside-an-object&quot;&gt;When you &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;puts&lt;/code&gt; inside an object&lt;/h2&gt;

&lt;p&gt;So, if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;puts&lt;/code&gt; is actually not a “global” method, but a private method which is included from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Kernel&lt;/code&gt; in every object, then… When you call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;puts&lt;/code&gt; from inside some other class’ method, &lt;em&gt;whose&lt;/em&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;puts&lt;/code&gt; is this?&lt;/p&gt;

&lt;p&gt;Of the object that calls it!&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;A&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:puts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;          &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; #&amp;lt;Method: A(Kernel)#puts(*)&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;#                                      ^ who owns the method actually&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:puts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;receiver&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; #&amp;lt;A:0x00...&amp;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;no&quot;&gt;A&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;nf&quot;&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is &lt;em&gt;different&lt;/em&gt; from most other languages (and, therefore, probably, from the default intuition of many developers), where “global” methods really do belong to some “global” scope and not to the current object.&lt;/p&gt;

&lt;p&gt;This might be considered just an esoteric internal quirk, but understanding this fact may be useful sometimes. Say, in testing some Text UI class, one might want to write test code that checks that some class prints elements of the UI on a method call. (There are special &lt;a href=&quot;https://rspec.info/features/3-13/rspec-expectations/built-in-matchers/output/&quot;&gt;RSpec matchers&lt;/a&gt; to check that, but let’s use this example for simplicity; there are many other Kernel methods that one might want to stub/expect in tests):&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;MyUI&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;header&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;-----&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;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MyUI&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:instance&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;n&quot;&gt;described_class&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;describe&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;#header&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;outputs header (would fail)&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# No, that `puts` from inside the class wouldn’t call Kernel.puts&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;expect&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;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;receive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:puts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;-----&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;instance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;header&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;outputs header (correct way)&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# because it owns its `puts`!&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;instance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;receive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:puts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;-----&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;instance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;header&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;div class=&quot;one-ukrainian-thing&quot;&gt;
  &lt;h3&gt;A postcard from 🇺🇦&lt;/h3&gt;

  &lt;p&gt;&lt;em&gt;&lt;strong&gt;Please stop here for a moment.&lt;/strong&gt; This is your regular mid-text reminder that I am a living person from Ukraine, with the Russian invasion still ongoing. Please read it.&lt;/em&gt;&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;One news item.&lt;/strong&gt; There are reliable reports that soldiers from North Korea (as many as 12000) &lt;a href=&quot;https://x.com/KhaterDiana/status/1847253931203670365&quot;&gt;are currently training in Russia to participate in the war in Ukraine&lt;/a&gt;.&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;One piece of context.&lt;/strong&gt; A year ago, on Oct 21, 2023, my best friend from the army training camp was killed in the front line. I wrote a bit about him in a mid-text “postcard” &lt;a href=&quot;https://zverok.space/blog/2023-11-03-syntax-sugar2-pattern-matching-fin.html&quot;&gt;here&lt;/a&gt;. Here is a small &lt;a href=&quot;https://x.com/zverok/status/1848284613048729777&quot;&gt;memorial Twitter thread&lt;/a&gt; about him.&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;One fundraiser.&lt;/strong&gt; Please help &lt;a href=&quot;https://x.com/zverok/status/1845118020110139857&quot;&gt;raise money&lt;/a&gt; for a volunteer, Olena Samoilenko, who helps the elderly, disabled, and other disadvantaged inhabitants of the Kherson region.&lt;/p&gt;
&lt;/div&gt;

&lt;h2 id=&quot;what-about-custom-top-level-methods&quot;&gt;What about custom top-level methods?&lt;/h2&gt;

&lt;p&gt;So, if all that looks like “standard global methods” are actually methods of the current object (included from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Kernel&lt;/code&gt;), what about &lt;em&gt;user-defined global methods&lt;/em&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;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;my_method&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;who am i? &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;The situation is almost exactly the same: such methods become &lt;em&gt;private instance methods&lt;/em&gt; of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Object&lt;/code&gt; class, and as such, &lt;em&gt;they are available in every object&lt;/em&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;nb&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:my_method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; #&amp;lt;Method: Object#my_method() test.rb:1&amp;gt;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;my_method&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# called in the context of the main object&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# prints: &quot;who am i? main&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;A&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;my_method&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;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;A&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;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:my_method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; #&amp;lt;Method: A(Object)#my_method() test.rb:1&amp;gt;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;test&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# invokes my_method that belongs to A&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# prints: who am i? #&amp;lt;A:0x0...&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So, once again: &lt;strong&gt;all top-level methods are actually present in every object&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is a clear and consistent system… Which might sometimes lead to extremely weird quirks with the metaprogramming code, which checks for some method’s presence by name and changes the behavior depending on it.&lt;/p&gt;

&lt;p&gt;We actually caught one just last week: deep in Rails insides, there was serialization code that relied on whether the current object &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;respond_to?(:avatar_url)&lt;/code&gt;—and in some completely unrelated place, a helper module was included into the global scope, which made &lt;em&gt;all&lt;/em&gt; objects to have &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;avatar_url&lt;/code&gt; method… But not the one that would be expected there in the serialization code. Extremely funny to debug!&lt;/p&gt;

&lt;p&gt;The outtake is: &lt;em&gt;keep your top-level scope clean of random methods, especially those with generic names, and including those that might come from included modules.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;How others do it:&lt;/strong&gt; To the best of my knowledge, there is no other languages (at least amongst relatively mainstream ones) with such an approach to “global” methods. Most of the OO languages either fully prohibit those (Java/C#, you only can have &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SomeClass.static_method&lt;/code&gt;); or there is no object context (no &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt;) in those methods (Kotlin, Python, PHP), or the methods are truly global, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this&lt;/code&gt; in them consistently refers to some global object (JS with its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;globalThis&lt;/code&gt;, Scala). But I might miss something!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;is-the-main-scope-special&quot;&gt;Is the main scope special?&lt;/h2&gt;

&lt;p&gt;The “main scope is special, everything there goes directly into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Object&lt;/code&gt; class” is a good enough heuristic to remember, but one might be interested to observe that the top-level code behaves like &lt;em&gt;any method body&lt;/em&gt;: as if it is all performed in a method of an instance of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Object&lt;/code&gt; class. In Ruby, you can have nested method definitions, but they don’t define local methods and instead go to 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;A&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;outer&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# See, we are defining helper inner method!&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;inner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&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;nb&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;iteration &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&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;p&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;nf&quot;&gt;times&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_1&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;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;A&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;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;outer&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# prints &quot;iteration 0&quot;, &quot;iteration 1&quot;, etc.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;inner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# prints &quot;iteration 1000&quot; -- the method is now defined in a!&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# ...and moreover&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;A&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;nf&quot;&gt;inner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# prints &quot;iteration 2000&quot; -- it belongs to the A class&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Like many things in Ruby, it is “unlike most of other languages, but self-consistent.”&lt;/p&gt;

&lt;p&gt;With this knowledge, we can model &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt; method and its definitions behavior this 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;n&quot;&gt;my_main&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;nf&quot;&gt;new&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;my_main&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;implicit_top_level&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# that&apos;s where all your top-level code go&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;other_method&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;OK!&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;my_main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;implicit_top_level&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# `my_main` is an instance of object,&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# so `other_method` is now defined in object&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# Let’s check:&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;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;other_method&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# prints &quot;OK!&quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# And as everything, including the top-level scope, inherits from Object,&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# we have it here:&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;other_method&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# prints &quot;OK!&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here is Ruby for you, keeping on its “slightly peculiar yet consistent” side for most of the time.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;This equivalence breaks when we talk about constants. All constants (including classes and modules) in the main scope are nested into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Object&lt;/code&gt;, too; but it wouldn’t work in our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main_scope_method&lt;/code&gt;. So… It is a bit special thing, after all! Or, rather, it behaves consistently with a method body for everything other than constants; and consistently with class/module body for constants.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;summarizing-it&quot;&gt;Summarizing it&lt;/h2&gt;

&lt;p&gt;Just to reiterate:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;In Ruby, there is no such thing as a top-level/global method; the method without an explicit receiver (the core one or user-defined one) is always a &lt;strong&gt;method of the current object&lt;/strong&gt;;&lt;/li&gt;
  &lt;li&gt;Methods defined on the top level become &lt;strong&gt;instance methods of every object&lt;/strong&gt;; modules &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;include&lt;/code&gt;d into the top-level scope, are included into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Object&lt;/code&gt;;&lt;/li&gt;
  &lt;li&gt;This distinction—that methods are never really “global”—can be mostly ignored, but it is useful to have an accurate mental model when using metaprogramming or debugging large codebases;&lt;/li&gt;
  &lt;li&gt;Everything is inspectable and should be inspected;&lt;/li&gt;
  &lt;li&gt;Many things are peculiar yet self-consistent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hope this helps :)&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;Thank you for reading. Please support Ukraine with your donations and lobbying for military and humanitarian help. &lt;a href=&quot;https://war.ukraine.ua/&quot;&gt;Here&lt;/a&gt;, you’ll find a comprehensive information source and many links to state and private funds accepting donations.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you don’t have time to process it all, donating to &lt;a href=&quot;https://savelife.in.ua/en/&quot;&gt;Come Back Alive&lt;/a&gt; foundation is always a good choice.&lt;/strong&gt;&lt;/p&gt;

&lt;iframe src=&quot;https://zverok.substack.com/embed&quot; width=&quot;480&quot; height=&quot;320&quot; style=&quot;border:1px solid #EEE; background:white;&quot; frameborder=&quot;0&quot; scrolling=&quot;no&quot;&gt;&lt;/iframe&gt;
&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;As we’ll see below, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;puts&lt;/code&gt; is a &lt;em&gt;private&lt;/em&gt; method, and in older versions of Ruby, you couldn’t call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self.private_method&lt;/code&gt;, only as a bare word (as a part of the general rule “no explicit receiver for private methods”); since Ruby 2.7, the requirement was relaxed to allow explicit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self.private_method&lt;/code&gt; (but only &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt;), see the Ruby Changes &lt;a href=&quot;https://rubyreferences.github.io/rubychanges/2.7.html#selfprivate_method&quot;&gt;entry on it&lt;/a&gt;. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;In Ruby, &lt;em&gt;unlike other languages&lt;/em&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;private&lt;/code&gt; methods are available to the children of the class who defined them, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;protected&lt;/code&gt; is used to mark what some other languages might call “friend”: methods that are available to the current object and &lt;em&gt;other objects of the same class&lt;/em&gt;. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;In some contexts, one needs a very lean class without any extra conveniences defined, and for that, the class might be explicitly inherited from &lt;a href=&quot;https://docs.ruby-lang.org/en/3.3/BasicObject.html&quot;&gt;BasicObject&lt;/a&gt;. But without this special approach, any class will inherit from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Object&lt;/code&gt;. &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Mon, 21 Oct 2024 00:00:00 +0000</pubDate>
        <link>https://zverok.space/blog/2024-10-21-global_functions.html</link>
        <guid isPermaLink="true">https://zverok.space/blog/2024-10-21-global_functions.html</guid>
        
        
        <category>ruby</category>
        
      </item>
    
      <item>
        <title>On programming and poetry (not Python’s tool)</title>
        <description>&lt;p&gt;&lt;strong&gt;Some thoughts on how programming’s unlikely relations to poetry, and some implications of those relations&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I don’t have much time recently to work on articles about programming (especially considering my typical article length); but I have some previously written content to share. This article was drafted as a &lt;a href=&quot;https://x.com/zverok/status/1493212494583713794&quot;&gt;Twitter thread&lt;/a&gt; on my 39th birthday: a day when I published my new site, and announced “I’ll be writing more here soon!” It was Feb 14, 2022, ten days before the full-scale Russian invasion started. Two and a half years later, I finally go to making it into a standalone text with clearer arguments, some links and conclusions. Anyway.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You don’t see poetry (as in writing and reading poems, not the Python tool) mentioned besides programming frequently. And when it is, it is usually in one of two ways:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;to contrast “soulful” humanitarian activity to the “boring” engineering one or&lt;/li&gt;
  &lt;li&gt;to use “poetry” as an informal synonym for “beauty,” to say that some code “reads like poetry” is to say it is nice, or expressive, or in some other way pleases the author of the statement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But I believe there could be deeper insights in looking at them together. Here are some assorted thoughts on it.&lt;/p&gt;

&lt;h2 id=&quot;am-i-qualified-to-write-about-the-topic&quot;&gt;Am I qualified to write about the topic?&lt;/h2&gt;

&lt;p&gt;Judge yourself.&lt;/p&gt;

&lt;p&gt;From the software development side: I am a software architect, writing code for 25+ years, a Ruby programming language committer, author of some open source libraries, many texts, and some documentation projects; in former days, I did a lot of Ruby mentoring, including teaching people who had no prior knowledge about Ruby/programming whatsoever.&lt;/p&gt;

&lt;p&gt;From the poetry/writing side: I have been writing prose and poetry for as long as I can remember myself; my first novel just had been published recently.&lt;/p&gt;

&lt;p&gt;I considered myself primarily a poet for at least a decade between my mid-20s and mid-30s. I didn’t achieve much more than occasional magazine publication or festival participation (almost got my first book published, but then the publisher went bankrupt). Nevertheless, I had enough community validation to at least believe I understand &lt;em&gt;some&lt;/em&gt; things about it (even if not excel myself).&lt;/p&gt;

&lt;p&gt;I stopped writing poetry a few years ago (and switched to prose, that’s a different story). Some of my recent texts can be &lt;a href=&quot;https://zverok.space/writing/old/&quot;&gt;here&lt;/a&gt; (mostly in Russian, which I don’t use anymore, obviously).&lt;/p&gt;

&lt;p&gt;I also organized a notable online poetry festival in 2017, before it was cool! (The site is down currently, and the remains of the festival are scrubbed from the internet for reasons too complicated to explain. However, I was exposed to a large number of authors from all over Western Europe who wrote in several Slavic languages—Serbian, Croatian, Bulgarian, Slovak, Polish, etc.—which was an educative experience in itself.)&lt;/p&gt;

&lt;p&gt;I still read a lot of poetry and ON poetry and try to understand the broad context; this year, I started the &lt;a href=&quot;https://7uapoems.substack.com/&quot;&gt;7uapoems&lt;/a&gt; project, dedicated to the translation of modern Ukrainian poetry written by those who are currently in the army.&lt;/p&gt;

&lt;p&gt;With that being said, I am not really strong on literary theory, so all attempts to define things in the text below are my own. For those well-versed (pun intended) in theoretical matters, it would probably be irritating or laughable.&lt;/p&gt;

&lt;p&gt;I don’t also try to go into precise theoretical terms on the software development point of view. This, I could’ve done, but my way of thinking and talking about things is usually “in layman’s terms,” for better or worse.&lt;/p&gt;

&lt;h2 id=&quot;why-do-we-write-and-read-literature&quot;&gt;Why do we write and read literature&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A loose attempt to define some things&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Why do we write and read literature, any literature?&lt;/p&gt;

&lt;p&gt;Mostly to &lt;em&gt;share an experience&lt;/em&gt;. An experience of going somewhere, living through some events, feeling some emotions, understanding some things. The experience we probably can’t access directly. (Yes, there is also room for a variety of literature, which just helps to relive familiar experiences, but let’s not complicate it further.)&lt;/p&gt;

&lt;p&gt;In the scope of this definition, &lt;em&gt;poetry&lt;/em&gt;, in a nutshell, is a more efficient way of sharing an experience that is hard to express rationally. It relies on some “leap” of understanding, where a carefully crafted phrase is as efficient as five dense pages of explanations. (“Oversimplification” is my middle name!)&lt;/p&gt;

&lt;p&gt;That has actually nothing to do with “beauty,” which is just one of the &lt;em&gt;poetic tools&lt;/em&gt; that can be used, rejected, or subverted; in bad poetry, “beautifully crafted” lines can mask the triviality of the meanings conveyed/experiences shared.&lt;/p&gt;

&lt;p&gt;While I stopped writing poetry, I still believe that the “poetic” way of passing the experience is the most efficient in many cases. And, to the best of my perception, I have never stopped doing &lt;em&gt;poetic work&lt;/em&gt; while writing prose. There are “prosaic” writing tools: structuring the plot, taking care of worldbuilding, and inventing compelling motivations for characters, and there are “poetic” tools: choosing the tone, rhythmand imagery that would immerse the reader—and both kinds of tools are likely to be present in any forms of writing, in different amounts.&lt;/p&gt;

&lt;p&gt;Conversely, if you don’t like to read poetry (or even don’t understand why one would do it), it might mean you might not have met the kind that communicates to you and your experience, or your temperament doesn’t require this particular form of writing. This is just a personal characteristic, and it doesn’t mean that you are never exposed to poetic tools of writing in other texts. And hopefully, this also doesn’t mean that there is nothing you can take from my comparisons.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/2024-10-06/izyum.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A fragment from Artur Dron’s poem, “Izyum communion”. &lt;a href=&quot;https://7uapoems.substack.com/p/seven-poems-a-week-week-2-artur-dron&quot;&gt;The translation&lt;/a&gt; is mine.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;how-is-programming-related&quot;&gt;How is programming related?&lt;/h2&gt;

&lt;p&gt;In the casual “engineering vs art” framework of thought, programming seems to oppose poetry.
And yet, in former years, every time I shyly mentioned at a programming conference “…and also a poet”, there were people around—frequently, distinguished engineers—to say “me too!”&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/2024-10-06/quote.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A quote via &lt;a href=&quot;https://x.com/alexbilz/status/1839860232593223983&quot;&gt;Alex Bilzerian&lt;/a&gt;, shown to me by my friend &lt;a href=&quot;https://adworse.codes/&quot;&gt;Dima Ermilov&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I am thinking and writing a lot about perceiving code as text and intuitions/approaches/expectations it brings. &lt;a href=&quot;https://zverok.space/blog/2015-09-22-three-kinds.html&quot;&gt;One of the first&lt;/a&gt; articles on this blog (nine years ago, huh) was about Ruby’s suitability for “writer” kind of programmers; my recent &lt;a href=&quot;https://2024.euruko.org/speakers/victor_shepelev&quot;&gt;EuRuKo talk&lt;/a&gt; also had “writing stories in code” as one of the key themes.&lt;/p&gt;

&lt;p&gt;Taking the definition of writing from earlier, &lt;strong&gt;we can say that writing code is &lt;em&gt;sharing the experience&lt;/em&gt; of understanding the requirements/implementation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Looking from this point, we can clearly see room for &lt;em&gt;poetic&lt;/em&gt; work here: it is frequently useful—at least with some mindsets—to start solving the task with the several “phrases” that make the leap over the whole implementation domain.&lt;/p&gt;

&lt;p&gt;The topic is quite divisive: to the extent when &lt;em&gt;any&lt;/em&gt; attempt to compress meaning with modern language features is frowned upon—especially sad to observe in the Ruby community, where the discrepancy between language’s intent and community practices seems to only grow with time.&lt;/p&gt;

&lt;p&gt;The “poetic” approaches get ghettoized in the languages more friendly to leaps of understanding but less readable as everyday prose, like Perl and APL. (Aside note: the original thread was partially triggered by the first episode of &lt;a href=&quot;https://www.arraycast.com/&quot;&gt;The Array Cast podcast&lt;/a&gt;, explicitly mentioning poetry in regards to APL and related languages. But even there, if I remember correctly, it was mentioned rather as a generic synonym for “something beautiful/pleasant to read,” though the users of terse languages might be, indeed, closer to agreeing with the value of “pass the experience in an intuitive way via carefully crafted line of text.”)&lt;/p&gt;

&lt;p&gt;The thing is, I believe that &lt;strong&gt;poetic work is a valid and useful tool.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sometimes, “poetic” code is useful in the drafting stage, and then it is expanded—fully or partially—into a more verbose “prose,” all the modules and services and concerns.&lt;/p&gt;

&lt;p&gt;Sometimes, the succinct, “intuitive” expression is the best, and it is acknowledged as such by most of the readers—the same way some lines of poems become proverbs. We frequently call these “idioms” in coding (I have my concerns with this term, but that’s for another time).&lt;/p&gt;

&lt;p&gt;It might be an uncommon opinion, but the amount of code solving the same task &lt;em&gt;might&lt;/em&gt; differ strikingly (talk orders of magnitude) if the “stop, think, write one verse” approach (instead of “write 20 pages of your usual”) matches the task.&lt;/p&gt;

&lt;p&gt;And maintaining five lines of code—even one that leans on intuitions—is, in any case, cheaper than maintaining 20 files of it. A big part of the industry at some point stopped believing that, so the “don’t write smart code” argument is usually argued as “2 lines of ‘simple’ code against 1 line of ‘smart’.”&lt;/p&gt;

&lt;div class=&quot;one-ukrainian-thing&quot;&gt;
  &lt;h3&gt;A postcard from 🇺🇦&lt;/h3&gt;

  &lt;p&gt;&lt;em&gt;&lt;strong&gt;Please stop here for a moment.&lt;/strong&gt; This is your regular mid-text reminder that I am a living person from Ukraine, with the Russian invasion still ongoing. Please read it.&lt;/em&gt;&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;One news item.&lt;/strong&gt; &lt;a href=&quot;https://united24media.com/latest-news/russian-forces-executed-16-ukrainian-prisoners-of-war-in-pokrovske-direction-2729&quot;&gt;Russian forces executed 16 Ukrainian prisoners of war in the Pokrovsk direction.&lt;/a&gt;&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;One piece of context.&lt;/strong&gt; &lt;a href=&quot;https://x.com/With__Ukraine/status/1842330762231599109&quot;&gt;How exactly Russia violates the Genocide Convention&lt;/a&gt;.&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;One fundraiser.&lt;/strong&gt; Please help to &lt;a href=&quot;https://x.com/WayToUkraine/status/1842364786144903273&quot;&gt;raise fund&lt;/a&gt; to buy a logistics truck! “Way To Ukraine” is a reliable fundraising organization with full transparency and a real understanding of what our military needs.&lt;/p&gt;
&lt;/div&gt;

&lt;h2 id=&quot;so-what&quot;&gt;So what?&lt;/h2&gt;

&lt;p&gt;Whether you sold on my comparison or not, you might wonder about its &lt;em&gt;utility&lt;/em&gt;. Like, OK, programming, or some kinds of it, might or might not bear some resemblance to “poetic work,” but where does it lead us?&lt;/p&gt;

&lt;p&gt;The thing is, I would like to see more people treating code on the level of phrases and “paragraphs” (methods or groups of statements) with the mindset of how the literature is treated. And I mean it not as just another way of saying, “let’s write bEAuTifuL code and be free as butterflies!”&lt;/p&gt;

&lt;p&gt;I am talking about the kind of perspective from which we can analyze, discuss (and maybe teach ourselves) to think in terms of the effect on the potential reader (both human and compiler/interpreter) in terms of “how meanings of expression, in this case, convey the idea behind the code; whether it could be expressed in a different way to emphasize a different aspect.” It is a conversation that is less rigid than autoformatting and style guides and more nuanced than abstract “readability” or “smart/simple code.”&lt;/p&gt;

&lt;p&gt;But time and again, when I have this conversation with people (colleagues or just participants of online discussions), it is a two-stop track: first, “it is all subjective, and just depends on who likes what,” and when presented with less-subjective analysis/explanation about how the text/code might be perceived, the second and final stop is “maybe, but I don’t understand why you pay so much attention to this, it is all nitpicking.”&lt;/p&gt;

&lt;p&gt;But I still believe this is a conversation worth having. I would like to see, for example, some “literary” criticism of new releases of a popular library, along the lines of “this is a stylistic approach this author usually takes, which works this way, and is perfect for tasks like A, yet in situation B it leads to those possible misunderstandings…”—but we can’t have that, without being labeled as “subjective” or “nitpicking,” because “well, it works, what else do you need?”&lt;/p&gt;

&lt;p&gt;We also need to recognize and cherish wild experimenting with form and style, the “poet’s poet” notion: code that, as it is, you’ll hardly use in production and in a team, but which helps to investigate the possibilities of the expressive power of your language, and master them to use appropriately.&lt;/p&gt;

&lt;p&gt;The end goal here, again, is not some abstract “beauty” or “engineering excellence” but pragmatic values we all share: maintainability, openness to change—things that are affected with clarity of understanding, which, in turn, depends on how the things are &lt;em&gt;expressed&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That’s why I return to the topic of code and text constantly and from different angles. Not for the last time, probably!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;See also: “&lt;a href=&quot;https://olano.dev/blog/do-programmers-dream-of-electronic-poems/&quot;&gt;Do programmers dream of electric poems?&lt;/a&gt;”&lt;/strong&gt;, an article by &lt;a href=&quot;https://olano.dev/&quot;&gt;Facundo Olano&lt;/a&gt; which investigates similar topics with somewhat different argumentation.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;Thank you for reading. Please support Ukraine with your donations and lobbying for military and humanitarian help. &lt;a href=&quot;https://war.ukraine.ua/&quot;&gt;Here&lt;/a&gt;, you’ll find a comprehensive information source and many links to state and private funds accepting donations.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you don’t have time to process it all, donating to &lt;a href=&quot;https://savelife.in.ua/en/&quot;&gt;Come Back Alive&lt;/a&gt; foundation is always a good choice.&lt;/strong&gt;&lt;/p&gt;

&lt;iframe src=&quot;https://zverok.substack.com/embed&quot; width=&quot;480&quot; height=&quot;320&quot; style=&quot;border:1px solid #EEE; background:white;&quot; frameborder=&quot;0&quot; scrolling=&quot;no&quot;&gt;&lt;/iframe&gt;
</description>
        <pubDate>Sun, 06 Oct 2024 00:00:00 +0000</pubDate>
        <link>https://zverok.space/blog/2024-10-06-poetry.html</link>
        <guid isPermaLink="true">https://zverok.space/blog/2024-10-06-poetry.html</guid>
        
        
        <category>philosophy</category>
        
      </item>
    
      <item>
        <title>“Gilded Rose” refactoring kata in Ruby — as if it is 2024</title>
        <description>&lt;p&gt;&lt;strong&gt;A “stories-first” approach to refactor a small yet complicated piece of business code&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Recently, I didn’t have time or resource for serious writing. I had plans for several long articles for the summer that gone, but unfortunately not many have came of that. But last night I have stumbled upon famous (so it seems, though I have never seen it before) refactoring kata, and had an impulse for trying my hands on that, writing down some thoughts on my ways of writing code along the way.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;the-kata&quot;&gt;The kata&lt;/h2&gt;

&lt;p&gt;“&lt;a href=&quot;https://github.com/emilybache/GildedRose-Refactoring-Kata&quot;&gt;Gilded Rose&lt;/a&gt;” is a famous refactoring kata that is available in many languages. It &lt;a href=&quot;https://github.com/emilybache/GildedRose-Refactoring-Kata/blob/main/GildedRoseRequirements.md&quot;&gt;goes like this&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;First an introduction to our system:&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;All &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;items&lt;/code&gt; have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SellIn&lt;/code&gt; value which denotes the number of days we have to sell the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;items&lt;/code&gt;&lt;/li&gt;
    &lt;li&gt;All &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;items&lt;/code&gt; have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Quality&lt;/code&gt; value which denotes how valuable the item is&lt;/li&gt;
    &lt;li&gt;At the end of each day our system lowers both values for every item&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;Pretty simple, right? Well this is where it gets interesting:&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;Once the sell by date has passed, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Quality&lt;/code&gt; degrades twice as fast&lt;/li&gt;
    &lt;li&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Quality&lt;/code&gt; of an item is never negative&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;“Aged Brie”&lt;/strong&gt; actually increases in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Quality&lt;/code&gt; the older it gets&lt;/li&gt;
    &lt;li&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Quality&lt;/code&gt; of an item is never more than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;50&lt;/code&gt;&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;“Sulfuras”&lt;/strong&gt;, being a legendary item, never has to be sold or decreases in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Quality&lt;/code&gt;&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;“Backstage passes”&lt;/strong&gt;, like aged brie, increases in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Quality&lt;/code&gt; as its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SellIn&lt;/code&gt; value approaches;
      &lt;ul&gt;
        &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Quality&lt;/code&gt; increases by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2&lt;/code&gt; when there are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;10&lt;/code&gt; days or less and by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3&lt;/code&gt; when there are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;5&lt;/code&gt; days or less but&lt;/li&gt;
        &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Quality&lt;/code&gt; drops to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt; after the concert&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;We have recently signed a supplier of conjured items. This requires an update to our system:&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;“Conjured”&lt;/strong&gt; items degrade in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Quality&lt;/code&gt; twice as fast as normal items&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;Feel free to make any changes to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UpdateQuality&lt;/code&gt; method and add any new code as long as everything still works correctly. However, do not alter the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Item&lt;/code&gt; class.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/emilybache/GildedRose-Refactoring-Kata/blob/main/ruby/gilded_rose.rb#L7-L54&quot;&gt;initial Ruby implementation&lt;/a&gt; provided by the kata maintainer is some scary 45-line method, all made of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt;s with many levels of nesting; it is a direct translation of the initial &lt;a href=&quot;https://github.com/emilybache/GildedRose-Refactoring-Kata/blob/main/csharp.NUnit/GildedRose/GildedRose.cs#L14&quot;&gt;C# implementation&lt;/a&gt;. The suggested exercise is to refactor it in any way one sees suitable.&lt;/p&gt;

&lt;p&gt;Several Ruby solutions to the kata can be found throughout the internet. Mostly, they take on the usual path of having a class per every item kind and frequently divide all the logic into five-word-name, one-line-body methods for each action in every class: that’s a style significant part of the community prefers lately.&lt;/p&gt;

&lt;p&gt;My approaches to writing in Ruby are somewhat different, and I thought that I might just show how I’d solve a task like that.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“But isn’t it just a useless toy task?” No parallelism, no SQL tinkering, no sub-microsecond benchmarking… In all honesty, surprising amounts of business code written in many modern codebases are solving simple yet convoluted tasks like that; and too frequently poor expression of thoughts on &lt;em&gt;this&lt;/em&gt; “negligible” level is what really affects velocity, stability and the design of the whole system. That’s why I am frequently talking about “phrase-level” expressiveness and method-size solutions, being principal developer and all.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;adding-unit-tests&quot;&gt;Adding unit tests&lt;/h2&gt;

&lt;p&gt;The code has “integrational” tests: a &lt;a href=&quot;https://github.com/emilybache/GildedRose-Refactoring-Kata/blob/main/ruby/texttest_fixture.rb&quot;&gt;script&lt;/a&gt; that prints the 30-day output for some items and an &lt;a href=&quot;https://github.com/emilybache/GildedRose-Refactoring-Kata/blob/main/texttests/ThirtyDays/stdout.gr&quot;&gt;expected output&lt;/a&gt; file (which the original kata suggested to check via the specialized tool, but in a scripting language, it is easy to make the &lt;a href=&quot;https://github.com/zverok/gilded_rose_kata/blob/main/texttest.rb&quot;&gt;checking script from scratch&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;It lacks more atomic tests, though, and that’s what we start with.&lt;/p&gt;

&lt;p&gt;My approach to tests is simple:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;I like TDD/BDD (in the loose understanding of “write the tests before changing the code”);&lt;/li&gt;
  &lt;li&gt;I treat my tests the way I treat my code: I want them to be easy to write and read, leveraging my language’s expressiveness to have the code as close to “clearly express the intention, and nothing else” as possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The latter is a surprisingly contrarian opinion in the latter years, at least in the Ruby community. I dedicated quite an &lt;a href=&quot;https://zverok.space/blog/2017-11-07-on-culture-of-bdd.html&quot;&gt;emotional article&lt;/a&gt; article to the matter once, and I wouldn’t repeat my arguments here. I’ll just say that in my experience and experience of my colleagues, these approaches work, and I am not planning to change them anytime soon.&lt;/p&gt;

&lt;p&gt;So, on the “clearly expresses the intention” topic. What I’d “theoretically” want to write in tests for this algorithm is something to the meaning:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;for an item like &lt;em&gt;this&lt;/em&gt;:
    &lt;ul&gt;
      &lt;li&gt;in N days, we should have these values&lt;/li&gt;
      &lt;li&gt;in M days, we should have those values&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;for another item:
    &lt;ul&gt;
      &lt;li&gt;in N days, we should…&lt;/li&gt;
      &lt;li&gt;…and so on.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s how I am &lt;a href=&quot;https://github.com/zverok/gilded_rose_kata/blob/main/gilded_rose_spec.rb&quot;&gt;doing it&lt;/a&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;nb&quot;&gt;require_relative&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;gilded_rose&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;moarspec&apos;&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;GildedRose&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;before&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;days&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gilded_rose&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update_quality&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;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:gilded_rose&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;no&quot;&gt;GildedRose&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;item&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;n&quot;&gt;alias_matcher&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:become&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:have_attributes&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;standard item&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:item&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;no&quot;&gt;Item&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;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;+5 Dexterity Vest&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sell_in&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;quality&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&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;n&quot;&gt;it_with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;days: &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;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;is_expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;become&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;sell_in: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;quality: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;19&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;n&quot;&gt;it_with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;days: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&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;n&quot;&gt;is_expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;become&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;sell_in: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;quality: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&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;n&quot;&gt;it_with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;days: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&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;n&quot;&gt;is_expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;become&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;sell_in: &lt;/span&gt;&lt;span class=&quot;o&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;ss&quot;&gt;quality: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8&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;n&quot;&gt;it_with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;days: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;15&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;n&quot;&gt;is_expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;become&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;sell_in: &lt;/span&gt;&lt;span class=&quot;o&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;ss&quot;&gt;quality: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&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;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;aged brie&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:item&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;no&quot;&gt;Item&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;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Aged Brie&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sell_in&lt;/span&gt;&lt;span class=&quot;o&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;n&quot;&gt;quality&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&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;n&quot;&gt;it_with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;days: &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;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;is_expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;become&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;sell_in: &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;ss&quot;&gt;quality: &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;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;it_with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;days: &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;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;is_expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;become&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;sell_in: &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;ss&quot;&gt;quality: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8&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;end&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# ...and so on&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This code uses &lt;a href=&quot;https://github.com/zverok/moarspec&quot;&gt;moarspec&lt;/a&gt;, my RSpec extensions library (which tries to be ideologically compatible with RSpec, just taking its ideas further), namely its recently added &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;it_with&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;This method is just a shortcut for what RSpec already has. 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;n&quot;&gt;it_with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;days: &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;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;is_expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;become&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;sell_in: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;quality: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;19&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&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;..corresponds to this vanilla RSpec 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;n&quot;&gt;context&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;when it is day 1&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:days&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;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;is_expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;become&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;sell_in: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;quality: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;19&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;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;…and is indispensable when you need to write a lot of tests for the same piece of logic, specifying the correspondence of various parameters to expected values.&lt;/p&gt;

&lt;p&gt;The rest, like implicit testing of the subject (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;it { is_expected.to&lt;/code&gt; implicitly applied to what is declared as a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;subject&lt;/code&gt; above), and expressive matcher is what RSpec already provides. Though again, using many of these tools isn’t that fashionable lately.&lt;/p&gt;

&lt;p&gt;This is not the only way to make a concise test for input/output pairs. Other options are truth tables, something along the lines of:&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;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;sell_in: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;quality: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;19&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;sell_in: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;quality: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;sell_in: &lt;/span&gt;&lt;span class=&quot;o&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;ss&quot;&gt;quality: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8&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;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;when it is day &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;day&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;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;before&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gilded_rose&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update_quality&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;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;is_expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;have_attributes&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;attrs&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;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;Or, using RSpec’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shared_examples&lt;/code&gt; feature:&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;shared_examples&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;change by day&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;day&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;attrs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;when it is day &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;day&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;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;before&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gilded_rose&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update_quality&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;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;is_expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;have_attributes&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;attrs&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;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;it_behaves_like&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;change by day&apos;&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;ss&quot;&gt;sell_in: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;quality: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;19&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;it_behaves_like&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;change by day&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;sell_in: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;quality: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Both of these options have their merits, but I preferred &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;it_with&lt;/code&gt; approach here because it doesn’t have any indirections (like, a pair of input/output in one place, and how they are related is in the other), so when something fails, it shows exactly the line that failed. If I deliberately break one of the tests, RSpec will clearly point to the line it is broken (I am deliberately changing one of the tests to be failing to showcase this):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/2024-09-19/red.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And the last line is exactly the command you need to restart only the failing test for quick debugging (which is harder with truth tables/shared examples).&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Note also pretty informative test and error description produced without any additional text written by the developer. It is not very grammatical, but pretty clear.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;the-implementation&quot;&gt;The implementation&lt;/h2&gt;

&lt;p&gt;With tests properly passing, now to implementation.&lt;/p&gt;

&lt;p&gt;When rereading the requirements, we might notice that &lt;strong&gt;all conditions can be described as a simple dependency &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(name, sell_in) =&amp;gt; change of quality&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The most natural representation of this in the modern Ruby would be pattern matching. So, quite quickly (with a few failing tests and numbers tinkering), I’ve come up with 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;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GildedRose&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;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@items&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;items&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;update_quality&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;update_item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_1&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;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;update_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;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&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;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Sulfuras, Hand of Ragnaros&quot;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;change&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;case&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;name&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;sell_in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Aged Brie&quot;&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;mi&quot;&gt;0&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;mi&quot;&gt;2&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Aged Brie&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&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;mi&quot;&gt;1&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Backstage passes to a TAFKAL80ETC concert&quot;&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;mi&quot;&gt;0&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;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;quality&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Backstage passes to a TAFKAL80ETC concert&quot;&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;mi&quot;&gt;5&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;mi&quot;&gt;3&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Backstage passes to a TAFKAL80ETC concert&quot;&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;mi&quot;&gt;10&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;mi&quot;&gt;2&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Backstage passes to a TAFKAL80ETC concert&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&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;mi&quot;&gt;1&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&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;mi&quot;&gt;0&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;mi&quot;&gt;2&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&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;mi&quot;&gt;1&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&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;sell_in&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&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;quality&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;quality&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;change&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;clamp&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;50&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;There aren’t many moving parts here:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;quick guard clause for “legendary item” that basically doesn’t require any processing;&lt;/li&gt;
  &lt;li&gt;almost literal rewriting of requirements into simple correspondences “with this name &amp;amp; range of values of how many days left: that change in quality”&lt;/li&gt;
  &lt;li&gt;the requirement “quality never goes below 0 and above 50” straightforwardly translates into &lt;a href=&quot;https://docs.ruby-lang.org/en/master/Comparable.html#method-i-clamp&quot;&gt;Comparable#clamp&lt;/a&gt;; this allows decoupling of &lt;em&gt;change&lt;/em&gt; of the value from the &lt;em&gt;range control&lt;/em&gt; (instead of the &lt;a href=&quot;https://github.com/emilybache/GildedRose-Refactoring-Kata/blob/main/ruby/gilded_rose.rb#L20-L21&quot;&gt;naive initial&lt;/a&gt; “if it is inside limits, do the operation”).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For further clarity, let’s walk through a part of it, with comments (shortened the name of the item so the code will take less place horizontally):&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;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;name&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;sell_in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# we match pair of (name, days to sell)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Backstage passes&quot;&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;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# if it matches (backstage pass, anything &amp;lt;=0)&lt;/span&gt;
  &lt;span class=&quot;o&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;quality&lt;/span&gt;                &lt;span class=&quot;c1&quot;&gt;# remove the rest of the quality&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Backstage passes&quot;&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;mi&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# otherwise, (backstage pass, anything &amp;lt;= 5)&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;c1&quot;&gt;# +3 points: I used (optional) + to underline the meaning&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Backstage passes&quot;&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;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# ...and so on...&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;                           &lt;span class=&quot;c1&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Backstage passes&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;     &lt;span class=&quot;c1&quot;&gt;# if name still matches, and ANY other days to sell&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;                           &lt;span class=&quot;c1&quot;&gt;# the fallback value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note that inside pattern matching, Ruby uses &lt;a href=&quot;https://docs.ruby-lang.org/en/master/Object.html#method-i-3D-3D-3D&quot;&gt;#===&lt;/a&gt; matching method for every value in an array, and beginless range (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;..0&lt;/code&gt;: range from -infinity to 0, including it) has its &lt;a href=&quot;https://docs.ruby-lang.org/en/master/Range.html#method-i-3D-3D-3D&quot;&gt;===&lt;/a&gt; defined so that it returns &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt; for a number inside the range.&lt;/p&gt;

&lt;p&gt;That’s, kinda, it! The core method is 25 lines now (including blank ones); at the same time, it is only four simple expressions and only one level of condition/nesting.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Random fun fact: Ruby linter/style checker Rubocop with default settings would complain that:&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;Cyclomatic complexity for update_item is too high. (10/7)&lt;/li&gt;
    &lt;li&gt;Method has too many lines. (22/10)&lt;/li&gt;
    &lt;li&gt;Perceived complexity for update_item is too high. (10/8)&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;Judge yourself whether the complexity and length of the code in front of your eyes is that criminal! (In my opinion, our obsession with “small simple methods with very descriptive names” went too far many years ago.)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One more notice: the original kata’s text suggests “not to re-write the code from scratch, but rather to practice taking small steps, running the tests often, and incrementally improving the design.” What I did, though, demonstrates one of the things I’ve learned to trust through my career: when meeting with really problematic code, it is frequently easier to rewrite [parts of] it from scratch, at least on a small scale. Iterative improvements might never converge to something that clearly says what the original intention was!&lt;/p&gt;

&lt;p&gt;If I hadn’t been provided with the requirements when meeting the code like that, I’d probably move for some time with iterating improvements till the original requirements would become clear—first and foremost, trying to make &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt; linear (even at the price of repeating conditions) to see how many branches are there &lt;em&gt;really&lt;/em&gt;; and then considered whether there is a better way to implement those requirements. (I tried to demonstrate that in a series of commits to a separate branch, but early on in the attempt, I understood I was not doing an honest job. I couldn’t shake away the knowledge about the requirements, so I was too quickly shortcutting to “oh, anyway, I know why this &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt; 50&lt;/code&gt; repeats in many branches”. If I stumbled upon the kata again, it would be fun to try to honestly restore the requirements from the code without reading them first. But it is what it is.)&lt;/p&gt;

&lt;h2 id=&quot;what-it-would-take-to-implement-a-new-requirement&quot;&gt;What it would take to implement a new requirement&lt;/h2&gt;

&lt;p&gt;Let’s remember that the whole kata’s goal was to make updating the code easier. So, back to that newly emerged requirement:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;We have recently signed a supplier of conjured items. This requires an update to our system:&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;“Conjured”&lt;/strong&gt; items degrade in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Quality&lt;/code&gt; twice as fast as normal items&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Specifying that will require adding this tests block:&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;describe&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;conjured item&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:item&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;no&quot;&gt;Item&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;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Conjured Mana Cake&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sell_in&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;quality&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;30&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;n&quot;&gt;it_with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;days: &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;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;is_expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;become&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;sell_in: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;quality: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;28&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;n&quot;&gt;it_with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;days: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&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;n&quot;&gt;is_expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;become&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;sell_in: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;quality: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&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;n&quot;&gt;it_with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;days: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&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;n&quot;&gt;is_expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;become&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;sell_in: &lt;/span&gt;&lt;span class=&quot;o&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;ss&quot;&gt;quality: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&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;n&quot;&gt;it_with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;days: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;15&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;n&quot;&gt;is_expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;become&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;sell_in: &lt;/span&gt;&lt;span class=&quot;o&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;ss&quot;&gt;quality: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&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;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;…and this to the main &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;case&lt;/code&gt; of the implementation:&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;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/Conjured/&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;mi&quot;&gt;0&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;mi&quot;&gt;4&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/Conjured/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&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;mi&quot;&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;(As explained above, pattern matching uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;===&lt;/code&gt; to check individual values, and &lt;a href=&quot;https://docs.ruby-lang.org/en/master/Regexp.html#method-i-3D-3D-3D&quot;&gt;Regexp#===&lt;/a&gt; can be utilized here to handle “any string that starts with”. Random fun fact: many nice refactorings suddenly break on the necessity to handle string inclusion instead of constant strings to identify the necessary case.)&lt;/p&gt;

&lt;p&gt;So, an eight-line change &lt;a href=&quot;https://github.com/zverok/gilded_rose_kata/commit/2eed8bec23289bc289c88b60b5bfa66b27f9d790&quot;&gt;in total, including blank lines&lt;/a&gt; (the commit is somewhat bigger as it also updates the integrational test).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/2024-09-19/allgreen.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Well, seems not that bad, actually…&lt;/p&gt;

&lt;div class=&quot;one-ukrainian-thing&quot;&gt;
  &lt;h3&gt;A postcard from 🇺🇦&lt;/h3&gt;

  &lt;p&gt;&lt;em&gt;&lt;strong&gt;Please stop here for a moment.&lt;/strong&gt; This is your regular mid-text reminder that I am a living person from Ukraine, with the Russian invasion still ongoing. Please read it.&lt;/em&gt;&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;One news item.&lt;/strong&gt; Photos emerged on social media showing &lt;a href=&quot;https://x.com/Lyla_lilas/status/1835999466844725474&quot;&gt;russians executed an unarmed captive Ukrainian soldier with a sword.&lt;/a&gt;&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;One piece of context.&lt;/strong&gt; Many international festivals decided to screen the “Russians at War” movie by “Canadian” director Anastasia Trofimova. It is claimed to be a documentary, but it is &lt;a href=&quot;https://x.com/United24media/status/1832126831585083428&quot;&gt;actually a propaganda piece by a person employed by RT Russian propaganda channel&lt;/a&gt;.&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;One fundraiser.&lt;/strong&gt; &lt;a href=&quot;https://x.com/zemlnk/status/1836274619944153198&quot;&gt;The PayPal fundraiser&lt;/a&gt; for the most critical Pokrovsk frontline direction.&lt;/p&gt;
&lt;/div&gt;

&lt;h2 id=&quot;is-that-really-it&quot;&gt;Is that really it?..&lt;/h2&gt;

&lt;p&gt;So, an honest question one might have: if it was real application code, would I really leave it like that, in one method with many conditions?&lt;/p&gt;

&lt;p&gt;With this amount and uncertainty of requirements, honestly: yes!&lt;/p&gt;

&lt;p&gt;One thing I am not happy about here, though, is the repetition of the name check in the pattern-matching solution. The possible alternative, which is kinda “DRYer”, is using ole good &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;case&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;when&lt;/code&gt; instead:&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;change&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&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;name&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Aged Brie&quot;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;case&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;sell_in&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;          &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Backstage passes to a TAFKAL80ETC concert&quot;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;case&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;sell_in&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;  &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;o&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;quality&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;  &lt;span class=&quot;k&quot;&gt;then&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;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;           &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;case&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;sell_in&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;          &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&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 solution has its pros and cons:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;pro: no duplication on name check; more visible logical blocks per item group;&lt;/li&gt;
  &lt;li&gt;con: duplication of the condition &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;case item.sell_in&lt;/code&gt;—which is somewhat harder to notice (that all the checks are by the same parameter) and somewhat easier to break (i.e., introduce another part of the logic that makes the decision by another variable and start losing the whole picture).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In real application, I might’ve chosen one of the two (or switch between them during development), depending on what feels better at the moment!&lt;/p&gt;

&lt;p&gt;With the growth of the cases number (or if I’d felt extra-architector-y), I might’ve also considered a data-based approach: construct a constant hash of conditions like…&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;s2&quot;&gt;&quot;Aged Brie&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&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;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&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;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&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;s2&quot;&gt;&quot;Backstage passes to a TAFKAL80ETC concert&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&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;mi&quot;&gt;0&lt;/span&gt;   &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:nullify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# a special value &quot;drop it to zero&quot;&lt;/span&gt;
    &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;=&amp;gt;&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;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&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;11&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&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;c1&quot;&gt;# ...and so on&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;…and then a code that chooses the change based on that.&lt;/p&gt;

&lt;p&gt;But only after understanding I have &lt;strong&gt;enough cases to decide&lt;/strong&gt; what is the exhaustive structure for such a constant (and if we really have one).&lt;/p&gt;

&lt;p&gt;Which leads us to the following question…&lt;/p&gt;

&lt;h2 id=&quot;but-what-about-asbstrakshunz&quot;&gt;But what about asbstrakshunz?&lt;/h2&gt;

&lt;p&gt;Apparently, the first intuitive reaction of many Rubyists (and not only them) to a task like that is thinking about representing each of the cases as a class that implements the corresponding piece of logic. It is so widespread that I have a term for it: knee-jerk OOP.&lt;/p&gt;

&lt;p&gt;I was like that, too. But with years, I learned to keep my OOP reflexes at bay—as well as &lt;em&gt;any&lt;/em&gt; early “let’s abstract it!” urge (like the aforementioned “constant extraction” idea).&lt;/p&gt;

&lt;p&gt;While the amount of code implementing some algorithm is no more than one screen, roughly, it is frequently beneficial to keep it all together to see the whole story at once. This way, when we don’t need to navigate through many classes/small methods, we can clearly see what variables change non-trivially (only &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;quality&lt;/code&gt;, with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sell_in&lt;/code&gt; being absolutely trivial), and variants of behavior we have (constant increase, constant decrease, nullify), and it is immediately obvious that we don’t (yet?) have cases like “quality halves every day” or “it depends on the weather”.&lt;/p&gt;

&lt;p&gt;I call it the “stories-first approach” (a slide from my recent &lt;a href=&quot;https://2024.euruko.org/speakers/victor_shepelev&quot;&gt;EuRuKo talk&lt;/a&gt;, I hope the video will be published soon):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/2024-09-19/stories.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Once there is more code (as the amount of conditions in the kata requirements is, of course, toy-ish), we might begin to see the appropriate structure for it, which might be quite different from the knee-jerk one! Would there be more “legendary” items, and would this mean the same thing every time (“just a constant quality and no &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sell_in&lt;/code&gt; change”) or a &lt;em&gt;new&lt;/em&gt; thing every time? What types of quality changes might be there, other than “increase/decrease/nullify”? Which types of the “change by” value changes are common, and which are accidental?&lt;/p&gt;

&lt;p&gt;Only having more than one screen of possible cases might we need to try finding things to extract. And quite frequently, the things extracted might happen to be small utilities, new “words” in the language the “story” is written in, instead of splitting one story into many independent ones!&lt;/p&gt;

&lt;p&gt;That’s it for now. Hope it was an interesting journey into another person’s thinking process, even if you disagree with some (or all) of the conclusions.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;Thank you for reading. Please support Ukraine with your donations and lobbying for military and humanitarian help. &lt;a href=&quot;https://war.ukraine.ua/&quot;&gt;Here&lt;/a&gt;, you’ll find a comprehensive information source and many links to state and private funds accepting donations.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you don’t have time to process it all, donating to &lt;a href=&quot;https://savelife.in.ua/en/&quot;&gt;Come Back Alive&lt;/a&gt; foundation is always a good choice.&lt;/strong&gt;&lt;/p&gt;

&lt;iframe src=&quot;https://zverok.substack.com/embed&quot; width=&quot;480&quot; height=&quot;320&quot; style=&quot;border:1px solid #EEE; background:white;&quot; frameborder=&quot;0&quot; scrolling=&quot;no&quot;&gt;&lt;/iframe&gt;
</description>
        <pubDate>Thu, 19 Sep 2024 00:00:00 +0000</pubDate>
        <link>https://zverok.space/blog/2024-09-19-gilded_rose.html</link>
        <guid isPermaLink="true">https://zverok.space/blog/2024-09-19-gilded_rose.html</guid>
        
        
        <category>ruby</category>
        
      </item>
    
      <item>
        <title>BuyMeACoffee silently dropped support for many countries, and nobody cares</title>
        <description>&lt;p&gt;&lt;strong&gt;Silent changes in payment methods on big creator funding platforms raise some unpleasant questions.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UPD Aug 13, 2024:&lt;/strong&gt; The kind-of-official reply from BuyMeACoffee, and &lt;a href=&quot;#upd-aug-13-2024&quot;&gt;my response to it&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UPD Aug 14, 2024:&lt;/strong&gt; A support email to one of the users gives BuyMeACoffee’s reasons, and also they &lt;a href=&quot;#upd-aug-14-2024&quot;&gt;blocked me on X (Twitter)&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;Recently, many Ukrainian creators have reported problems with payouts from &lt;a href=&quot;https://buymeacoffee.com/&quot;&gt;BuyMeACoffee&lt;/a&gt;, a creator funding/crowdfunding platform.&lt;/p&gt;

&lt;p&gt;At first, the reported support answers were typical corporative “we are sorry that we don’t care,” citing “compliance” and “policy updates.”&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; data-conversation=&quot;none&quot;&gt;&lt;p lang=&quot;uk&quot; dir=&quot;ltr&quot;&gt;В оригіналі, щоб вестерни читали які там є нікчеми &lt;a href=&quot;https://t.co/JxoGgamYLq&quot;&gt;pic.twitter.com/JxoGgamYLq&lt;/a&gt;&lt;/p&gt;&amp;mdash; Karå ꑭ🇺🇦 (@BzickOff) &lt;a href=&quot;https://twitter.com/BzickOff/status/1819341225024983310?ref_src=twsrc%5Etfw&quot;&gt;August 2, 2024&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;Those reports were widely shared, and our predominantly feeling was “shocked but not surprised”: it is not the first time when a big platform has decided that they are too busy to try to distinguish non-occupied parts of a large country with a 30+m population from occupied parts, and decided they don’t care. Just recently, for example, the Mercury payment system &lt;strong&gt;&lt;a href=&quot;https://x.com/United24media/status/1815707715307479244&quot;&gt;just blacklisted the entirety of Ukraine&lt;/a&gt;&lt;/strong&gt; alongside Iran and North Korea.&lt;/p&gt;

&lt;p&gt;But in a few days, it turned out (again, from support messages shared on Twitter) that BuyMeACoffee &lt;strong&gt;just dropped support for Payoneer&lt;/strong&gt; (which works in Ukraine), leaving Stripe (which doesn’t) as the &lt;strong&gt;only payout method&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; data-conversation=&quot;none&quot;&gt;&lt;p lang=&quot;uk&quot; dir=&quot;ltr&quot;&gt;4. Бо бай мі кохве мала в гузно своїх споживачів та закрила можливість виводити кошти напряму на карту через Wise, а також через Payoneer. Єдино можливий метод виводу стає сервіс Stripe який не доступний в Україні та до 14 серпня всі автори зможуть ввостаннє вивести свої кошти. &lt;a href=&quot;https://t.co/XIQdNHVllu&quot;&gt;pic.twitter.com/XIQdNHVllu&lt;/a&gt;&lt;/p&gt;&amp;mdash; AdrianZP (@AdrianZPcity) &lt;a href=&quot;https://twitter.com/AdrianZPcity/status/1821252119875436595?ref_src=twsrc%5Etfw&quot;&gt;August 7, 2024&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;h2 id=&quot;did-they-though&quot;&gt;Did they, though?&lt;/h2&gt;

&lt;p&gt;Currently BuyMeACoffee’s &lt;a href=&quot;https://help.buymeacoffee.com/en/articles/6258038-supported-countries-for-payouts-on-buy-me-a-coffee&quot;&gt;support page&lt;/a&gt; simply states:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Buy Me a Coffee supports payouts to creators in several countries, facilitated through our payment provider, Stripe.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;According to the Internet Archive, in &lt;a href=&quot;https://web.archive.org/web/20240229183241/https://help.buymeacoffee.com/en/collections/1907909-payments&quot;&gt;February&lt;/a&gt;, there was another link to a &lt;a href=&quot;https://web.archive.org/web/20231028113814/https://help.buymeacoffee.com/en/articles/6258038-payment-supported-countries&quot;&gt;page&lt;/a&gt; that listed Stripe and Payoneer, while &lt;a href=&quot;https://web.archive.org/web/20240521195730/https://help.buymeacoffee.com/en/collections/1907909-payments&quot;&gt;May’s snapshot&lt;/a&gt; already has only Stripe-related links.&lt;/p&gt;

&lt;p&gt;So, the change &lt;em&gt;in the documentation&lt;/em&gt; happened somewhere between February (that’s when I personally used it for the last time) and May: Internet Archive doesn’t provide other snapshots between those two dates.&lt;/p&gt;

&lt;p&gt;So, at least the support’s private answers were aligned with the documentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;At the same time, there are a couple of funny facts about this deprecation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First,&lt;/strong&gt;  I know for a fact that some Ukrainian creators were receiving payouts via Payoneer somewhere around ~1 month ago; I even saw a credible claim about last week! (I just sent mine, which trickled from a moderately popular blog, will see whether it will come through; it is funny if it will. At least my configured Payoneer payout method is currently active in the BMaC dashboard.)&lt;/p&gt;

&lt;p&gt;But more importantly, &lt;strong&gt;there was NO communication about the change&lt;/strong&gt;. You wouldn’t find anything on &lt;a href=&quot;https://x.com/buymeacoffee&quot;&gt;their Twitter&lt;/a&gt; or in the &lt;a href=&quot;https://building.buymeacoffee.com/changelog/&quot;&gt;public changelog&lt;/a&gt; (screenshotted at 2024-08-08 11:05:30 GMT+0300—just in case something will magically appear there retroactively):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/2024-08-08-bmac/image00.png&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;/img/2024-08-08-bmac/image01.png&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;/img/2024-08-08-bmac/image02.png&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;/img/2024-08-08-bmac/image03.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;As you can see, &lt;strong&gt;there is nothing about payout updates&lt;/strong&gt; after the &lt;a href=&quot;https://building.buymeacoffee.com/changelog/improved-payout-experience&quot;&gt;Nov’23 post&lt;/a&gt;, which mentioned Payoneer and Wise as available options alongside Stripe.&lt;/p&gt;

&lt;p&gt;I assure you that &lt;strong&gt;there was no email communication to the creators either&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;how-bad-is-this&quot;&gt;How bad is this?&lt;/h2&gt;

&lt;p&gt;The difference is public lists of supported countries between &lt;a href=&quot;https://www.payoneer.com/resources/global-payment-capabilities/&quot;&gt;Payoneer&lt;/a&gt; and &lt;a href=&quot;https://help.buymeacoffee.com/en/articles/6258038-supported-countries-for-payouts-on-buy-me-a-coffee&quot;&gt;Stripe&lt;/a&gt; are &lt;strong&gt;95 (ninety-five)&lt;/strong&gt; countries and territories. I am not sure that all 95 of those don’t have working Stripe (say, maybe the Faroe Islands just listed on Payoneer separately while being actually served by Stripe via Danish banks?), but it seems to be &lt;em&gt;a lot&lt;/em&gt; anyway.&lt;/p&gt;

&lt;p&gt;I’d like to know what the experience of BMaC users from those countries is like. But what I know for sure is that in Ukraine, a lot of people use the platform as a source of income (sometimes even a primary one). My subscriptions, for example, include but are not limited to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A singer-songwriter turned paramedic who uses BMaC donations to record her songs on short vacations between duties;&lt;/li&gt;
  &lt;li&gt;A writer and culturology scholar turned soldier who funds his studies about Ukrainian culture and history;&lt;/li&gt;
  &lt;li&gt;A girl whose two brothers have fallen on the frontlines, and she funds her absolutely unique book club to connect to people and share reading experience with them;&lt;/li&gt;
  &lt;li&gt;A small business owner from Kharkiv who uses a “build in public” approach for a small coffee shop (and recently got mobilized into Armed Forces);&lt;/li&gt;
  &lt;li&gt;…and so on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of them have BMaC as their primary income, and still, it is a sign of support from their peers and the possibility to continue doing what’s important to them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There is no way for any of us to receive the money accumulated on BMaC&lt;/strong&gt; anymore. What looks like “technical” change for some American is realistically &lt;strong&gt;prohibitive&lt;/strong&gt; for those not privileged enough to live in Stripe-supported countries. The money is still “there”, so probably no lawyer can say BMaC has “stolen them”—you just can’t neither receive “your” money nor, at least, give them back to those who sent them.&lt;/p&gt;

&lt;p&gt;Note that some BMaC accounts have a lot of supporters, and many of those use “yearly” payments as a sign of their support—and all of it is currently in some technological limbo.&lt;/p&gt;

&lt;p&gt;Now, I am just a mere developer, and I don’t know much about payout regulations and legal facts. I &lt;em&gt;might&lt;/em&gt; charitably assume there was a good reason for dropping Payoneer support (maybe a business one, or maybe it is actually related to some government regulations). The fact itself is extremely distressing for many, but hey, that’s their business, what do we know.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But the way this policy was implemented—no prior notice, no choice, no explicit communication of the reasons and possibilities—is absolutely fascinating, to say the least.&lt;/strong&gt; And, honestly, support’s evasive behavior (and inconsistent reports about “some Payoneer payouts might still work”) just adds insult to injury.&lt;/p&gt;

&lt;p&gt;I am not sure that any service that handles people’s money as their &lt;em&gt;primary&lt;/em&gt; activity, and that does it this way, can be ever trusted. Just sayin’.&lt;/p&gt;

&lt;p&gt;Have a good day, and have a screenshot of the latest message from BuyMeACoffee’s ever-positive Twitter!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/2024-08-08-bmac/image04.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Such a nice vibe! &lt;em&gt;(Neither their primary account nor &lt;a href=&quot;https://x.com/jijosunny&quot;&gt;the founder’s personal one&lt;/a&gt;, which both have a history of replying to all relevant tweets, are currently giving any signs they are aware of the situation.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;PS: As a side note, &lt;a href=&quot;https://en.ain.ua/2023/04/10/what-is-wrong-with-patreon-and-why-ukrainians-urge-canceling-it/&quot;&gt;here is a story&lt;/a&gt; about why Patreon is not an option for Ukrainians. And &lt;a href=&quot;https://www.patreon.com/wargonzoo&quot;&gt;here&lt;/a&gt; is russian war correspondent’s Patreon account, mentioned in the story, still proudly present on the platform (though seemingly dormant).&lt;/p&gt;

&lt;h2 id=&quot;upd-aug-13-2024&quot;&gt;UPD: Aug 13, 2024&lt;/h2&gt;

&lt;p&gt;So, folks! After a week of poking, we have a kind-of-official answer from &lt;a href=&quot;https://x.com/buymeacoffee&quot;&gt;@buymeacoffee&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Let’s unpack it!&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; data-conversation=&quot;none&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;This is simply not true. Let us clarify:&lt;br /&gt;&lt;br /&gt;1. We never hold funds. If for some reason we cannot payout (potential fraud, bank account issues), the amount is refunded.&lt;br /&gt;&lt;br /&gt;2. Not one Ukrainian creator&amp;#39;s payout is held. We paid out to thousands of Ukrainian creators this week. Please…&lt;/p&gt;&amp;mdash; Buy Me a Coffee (@buymeacoffee) &lt;a href=&quot;https://twitter.com/buymeacoffee/status/1823085434605605055?ref_src=twsrc%5Etfw&quot;&gt;August 12, 2024&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;&lt;strong&gt;0.&lt;/strong&gt; For some reason, this answer is a reply to one of those repeatedly asked them. Not even a standalone X post, nor a blog/changelog entry, nor a newsletter to creators.&lt;/p&gt;

&lt;p&gt;So, if you aren’t a creator who constantly buggers them on X-formerly-Twitter (or follows all the discussions), you might still be unaware of the policy change or the existence of some answers.&lt;/p&gt;

&lt;p&gt;The official feeds are still clear of the topic.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;ol&gt;
    &lt;li&gt;We never hold funds. If […] we cannot payout […] the amount is refunded.&lt;/li&gt;
  &lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;I believe this &lt;strong&gt;intention&lt;/strong&gt; (well, anything less would be a legal suicide), but I would still like to hear:
a. How the “cannot payout” decided and whether “the creator doesn’t have payout method we like, they should try another one” qualifies
b. A success story of real refunding (say, $5 to thousands of people) and whether it really works or just a claim.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;ol&gt;
    &lt;li&gt;Not one Ukrainian creator’s payout is held. […] Please reply with the creator @ if you know of any.&lt;/li&gt;
  &lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;I &lt;strong&gt;want to believe&lt;/strong&gt; that. In my original thread, and many other threads and discussions, I saw a LOT of people with negative experience of interaction with support.&lt;/p&gt;

&lt;p&gt;Hopefully, &lt;strong&gt;ALL&lt;/strong&gt; of Ukrainian creators who reported payout problems and unhelpful support responses are paid now? (And &lt;strong&gt;hopefully&lt;/strong&gt;, it is not by means of “just find an EU friend who will agree to receive a payment for you”?.. Because I saw stories like that).&lt;/p&gt;

&lt;p&gt;Also, dear BuyMeACoffee, “please @-us if you know a creator who has problems” would be &lt;strong&gt;a lot more persuasive&lt;/strong&gt; if it wasn’t in the effing &lt;em&gt;replies under just one tweet&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Just sayin’&lt;/p&gt;

&lt;blockquote&gt;
  &lt;ol&gt;
    &lt;li&gt;Almost all of our creators use our standard payout method powered by Stripe.&lt;/li&gt;
  &lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yeah, the so-called “First World”. But I get it. I do.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;We’ve decided to focus on this for […] reasons which are important for the sustainability of the business.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s your business, and that’s your right to do so. As inconvenient as it might be for us, we have no say here. But the way it was communicated…&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;we’re planning to continue offering an alternative payout method […] for countries that are not supported. As we figure this out, we can confirm that it is business as usual for creators from Ukraine&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It would be such a nice message if communicated clearly &amp;amp; timely 🙏&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;We’d appreciate it if you stop spamming our socials, spreading misinformation about us such as “we blocked withdrawals to Ukraine”. We’ll have no option but to block you and move on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You know what?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We’d appreciate&lt;/strong&gt; if you would be clear, honest, and public about the changes you make.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We’d appreciate&lt;/strong&gt; your support not changing the story from the initial “it’s compliance standards”, to further “your payout method is not supported, it is your problem” to final (after the public outrage) “oh of course we would help everyone 🙏”.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We’d appreciate&lt;/strong&gt; the public communication of the situation (and no, the reply in one thread in X that ends with passive aggression is not it, thank you very much!)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We’d appreciate&lt;/strong&gt; having this convo, like, a couple of weeks ago.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In conclusion&lt;/strong&gt;, I don’t believe BMaC being malicious or scammy in this situation (and I &lt;em&gt;do want&lt;/em&gt; to believe they are looking for a solution that will work for us, too). But the amount of neglect and disregard that was displayed in the way the change was implemented and communicated, and in the interactions with support, makes it almost a textbook case of &lt;strong&gt;bad communication&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;upd-aug-14-2024&quot;&gt;UPD: Aug 14, 2024&lt;/h2&gt;

&lt;p&gt;One of the Ukrainian creators received an unusually honest explanation of the reasons behind dropping support for Payoneer and Wise: “Incompatible with our upcoming features.” So, like, a bit more convenience for the First World users, at a price of excluding those not that privileged. (Note also that in this communication there is no sign of that promise of that “planning to continue offering an alternative payout method […] for countries that are not supported” which was sweetening their &lt;strong&gt;public&lt;/strong&gt; communication.)&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;I am disabling &lt;a href=&quot;https://twitter.com/buymeacoffee?ref_src=twsrc%5Etfw&quot;&gt;@buymeacoffee&lt;/a&gt; account tomorrow.&lt;br /&gt;&lt;br /&gt;The company is irresponsibly punches its Ukrainian clients in the guts by disabling Wise and making payouts impossible.&lt;br /&gt;&lt;br /&gt;This isn’t just about switching platforms; each Ukrainian creator will lose their entire subscriber base. This… &lt;a href=&quot;https://t.co/BPnuAgcutk&quot;&gt;pic.twitter.com/BPnuAgcutk&lt;/a&gt;&lt;/p&gt;&amp;mdash; Kate from Kharkiv (@BohuslavskaKate) &lt;a href=&quot;https://twitter.com/BohuslavskaKate/status/1823303795322822679?ref_src=twsrc%5Etfw&quot;&gt;August 13, 2024&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;&lt;strong&gt;And another update:&lt;/strong&gt; After several days of persistent, yet polite, poking their public X account, asking for public statement (more public than a reply in a thread), BuyMeACoffee &lt;a href=&quot;https://x.com/zverok/status/1823757570240340466&quot;&gt;just blocked me&lt;/a&gt;, reportedly “for spreading misinformation”:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/2024-08-08-bmac/image05.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Note there is new, previously never stated, pieces of information: about Payoneer working till Nov.1—not “do your last payout in August,” as it was previously; and about Wise which they will “continue to offer” (currently, to the best of my understanding, they aren’t).&lt;/p&gt;

</description>
        <pubDate>Thu, 08 Aug 2024 00:00:00 +0000</pubDate>
        <link>https://zverok.space/blog/2024-08-08-bmac-snafu.html</link>
        <guid isPermaLink="true">https://zverok.space/blog/2024-08-08-bmac-snafu.html</guid>
        
        
        <category>war</category>
        
        <category>rant</category>
        
      </item>
    
  </channel>
</rss>
