<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Giovanni Crisalfi</title>
    <description>The latest articles on Forem by Giovanni Crisalfi (@gicrisf).</description>
    <link>https://forem.com/gicrisf</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F493417%2F8482a0ed-2f1e-4cb5-a561-7719b098ec3e.png</url>
      <title>Forem: Giovanni Crisalfi</title>
      <link>https://forem.com/gicrisf</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/gicrisf"/>
    <language>en</language>
    <item>
      <title>Emacs Indigo: bindings for the Indigo cheminformatics library</title>
      <dc:creator>Giovanni Crisalfi</dc:creator>
      <pubDate>Sun, 09 Nov 2025 12:53:16 +0000</pubDate>
      <link>https://forem.com/gicrisf/emacs-indigo-bindings-for-the-indigo-cheminformatics-library-1hlh</link>
      <guid>https://forem.com/gicrisf/emacs-indigo-bindings-for-the-indigo-cheminformatics-library-1hlh</guid>
      <description>&lt;p&gt;After several months of development, I'm sharing the first pre-release of emacs-indigo, a native Emacs module that brings the &lt;a href="https://lifescience.opensource.epam.com/indigo/" rel="noopener noreferrer"&gt;Indigo cheminformatics library&lt;/a&gt; to Emacs Lisp. It's now at a point where the core functionality is solid enough to share.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;The package provides Emacs Lisp bindings to Indigo through a native C module. You can work with molecules directly in Emacs: load structures, convert between formats (SMILES, MOL, CML), calculate properties, perform substructure matching, and render visualizations.&lt;/p&gt;

&lt;p&gt;Memory management is handled automatically through &lt;code&gt;indigo-let*&lt;/code&gt;, a resource management macro that takes care of cleanup even when working with persistent molecule handles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;indigo-let*&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="ss"&gt;:molecule&lt;/span&gt; &lt;span class="nv"&gt;mol&lt;/span&gt; &lt;span class="s"&gt;"CCO"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:atoms&lt;/span&gt; &lt;span class="nv"&gt;atoms&lt;/span&gt; &lt;span class="nv"&gt;mol&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;indigo-map&lt;/span&gt; &lt;span class="nf"&gt;#'&lt;/span&gt;&lt;span class="nv"&gt;indigo-symbol&lt;/span&gt; &lt;span class="nv"&gt;atoms&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;;; =&amp;gt; ("C" "C" "O")&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Current implementation
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Core molecular operations (properties, calculations, format conversions)&lt;/li&gt;
&lt;li&gt;Iterator system for structure traversal (atoms, bonds, rings, stereocenters)&lt;/li&gt;
&lt;li&gt;Rendering and visualization (SVG, PNG output)&lt;/li&gt;
&lt;li&gt;Reaction handling and atom mapping&lt;/li&gt;
&lt;li&gt;Format support: SMILES, MOL, CML, reaction SMILES&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm currently working on a lazy stream abstraction for idiomatic iterator handling, avoiding the need to eagerly copy results into temporary lists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;Installation was a key focus: in the end, I reduced it to a simple one-liner that only requires GCC/Make and an Emacs installation with module support. The build system handles Indigo dependencies automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Status
&lt;/h2&gt;

&lt;p&gt;This is an early implementation. Core functionality is solid enough and ready for experimentation, though some features (PKA functions, advanced analysis) still need work. I'd welcome feedback from anyone working at the intersection of chemistry and Emacs.&lt;/p&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/gicrisf/emacs-indigo" rel="noopener noreferrer"&gt;https://github.com/gicrisf/emacs-indigo&lt;/a&gt;&lt;/p&gt;

</description>
      <category>emacs</category>
      <category>lisp</category>
      <category>c</category>
      <category>chemistry</category>
    </item>
    <item>
      <title>Windmenu: A Minimalist Windows Launcher</title>
      <dc:creator>Giovanni Crisalfi</dc:creator>
      <pubDate>Tue, 28 Oct 2025 19:11:25 +0000</pubDate>
      <link>https://forem.com/gicrisf/windmenu-a-minimalist-windows-launcher-1b4d</link>
      <guid>https://forem.com/gicrisf/windmenu-a-minimalist-windows-launcher-1b4d</guid>
      <description>&lt;p&gt;I've spent years bouncing between different operating systems, and I always found a pillar in dmenu's simplicity and portability. That minimal, keyboard-driven launcher that lets you summon any application without touching the mouse. I wanted something like that on Windows too.&lt;/p&gt;

&lt;h2&gt;
  
  
  On Launchers
&lt;/h2&gt;

&lt;p&gt;An operating system should be an extension of volition: you think of a program, a fragment of data, and it manifests in the fewest steps possible, with the least amount of latency possible. It should aspire to be like a mental glove that transduces intent into execution, orchestrating a rhizome of tapered mechanical fingers in digital space.&lt;/p&gt;

&lt;p&gt;When you approach the keyboard, you already know what you want to run: a browser, a text editor, a music player. Running a program is the very first action you take. On Linux desktops, this immediacy is understood, and not just in minimalist tiling window managers for power users, but in mainstream environments too.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmxp0w86chn3yedd7snjs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmxp0w86chn3yedd7snjs.png" alt="GNOME 40 Activities Overview on login" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;KDE demonstrates this perfectly: while offering comprehensive mouse-driven interaction, it also provides &lt;a href="https://userbase.kde.org/Plasma/Krunner" rel="noopener noreferrer"&gt;KRunner&lt;/a&gt;, a keyboard-driven launcher so smooth I wouldn't even need dmenu there. GNOME, on the other hand, pursues an even deeper level of immediacy, since it &lt;a href="https://discourse.gnome.org/t/gnome-40-login-is-to-the-activities-overview-mode-how-do-you-disable-this/5783" rel="noopener noreferrer"&gt;drops you straight into the activities overview when you login into the system&lt;/a&gt;. Clean, direct.&lt;/p&gt;

&lt;p&gt;But the built-in Windows search? It's a mess. Microsoft has been &lt;a href="https://devblogs.microsoft.com/react-native/rnw-settings-win11/" rel="noopener noreferrer"&gt;incorporating React Native into Windows 11&lt;/a&gt; for parts of the Settings app and Start Menu. They're spinning up a JavaScript runtime and rendering engine for system UI components. On first launch in a session, if your CPU is busy, it visibly lags. The previous menu weren't perfect, but they were lighter on system resources. The new one loads ads, web results, and "helpful" suggestions you never asked for, all while consuming CPU cycles that could be doing actual work. And if you don't disable web search, it lags whenever you search for anything - doesn't matter how powerful your machine is, doesn't matter the state of the CPU.&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Someone might say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Computers are powerful nowadays, you can't even notice that!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Wrong. You can notice, and that line of thinking is why we have paradoxically less performant commercial OS than 30 years ago.&lt;/p&gt;

&lt;p&gt;My own ritual, for example, usually begins with Emacs; when I'm on Windows, this means running the GUI version through WSL-1 and a X server. I didn't want to open a terminal emulator to launch a GUI: a hotkey, some typing, done. That's how it should be. &lt;sup id="fnref2"&gt;2&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;This specific need actually drove the whole custom command system. If a launcher couldn't handle arbitrary commands, it couldn't solve my problem; and that's what I wanted to achieve on Windows: to transplant the actual dmenu philosophy - a rectangle with text, high contrast, no translucent blur effects, no fade animations, no compositing - just a well-contrasted surface with program names that appears instantly when you press the hotkey.&lt;/p&gt;

&lt;p&gt;There are third-party launchers for Windows already: PowerToys Run has beautiful UI, but beauty wasn't the point. I wanted minimalism: do one thing (launch programs or scripts), do it well, do it fast. This meant building with pure Win32 APIs. No frameworks, no rendering engines, no external UI libraries. Something lean enough it could theoretically run on Windows 3.1.&lt;/p&gt;

&lt;h2&gt;
  
  
  Win32 UI
&lt;/h2&gt;

&lt;p&gt;I started from the UI. Creating windows, rendering text, drawing rectangles, managing keyboard input. It worked, but it was non-trivial and painful to do in Rust with a lot of unsafe calls. Then I found &lt;a href="https://github.com/JerwuQu/wlines" rel="noopener noreferrer"&gt;wlines&lt;/a&gt; by JerwuQu: a dmenu-like launcher for Windows, written in pure C with only Win32 APIs. Exactly what I wanted.&lt;/p&gt;

&lt;p&gt;This reframed the problem. Instead of building a monolithic launcher, I could split it into two pieces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Application launcher&lt;/strong&gt; (windmenu): Discovers programs, handles hotkeys, manages execution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generic selector&lt;/strong&gt; (wlines): Displays items, handles navigation, returns selection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Windmenu's job became clear: organize input for wlines (the list of available programs) and manage the output (execute the selected program).&lt;/p&gt;

&lt;h2&gt;
  
  
  Wlines as Subprocess
&lt;/h2&gt;

&lt;p&gt;The initial implementation was straightforward. Windmenu would build a command-line invocation of wlines with all the menu items and configuration flags, spawn it as a subprocess, and read the selected item from stdout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simplified version of the original approach&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;process&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Stdio&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"wlines.exe"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"-sbg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"#285577"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"-sfg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"#ffffff"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"-p"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Select option:"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="nf"&gt;.stdin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Stdio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;piped&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="nf"&gt;.stdout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Stdio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;piped&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="nf"&gt;.spawn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="py"&gt;.stdin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="nf"&gt;.write_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;b"Option 1&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Option 2&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Option 3"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="nf"&gt;.wait_with_output&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;selection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;str&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_utf8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="py"&gt;.stdout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This worked. It was performant on my development machine. But on older machines with mechanical drives, or systems under load, the lag was noticeable. The bottleneck was the process creation overhead. Every time you pressed the hotkey:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Windows had to create a new process&lt;/li&gt;
&lt;li&gt;Load the wlines executable from disk&lt;/li&gt;
&lt;li&gt;Initialize the UI subsystem&lt;/li&gt;
&lt;li&gt;Parse all the command-line arguments&lt;/li&gt;
&lt;li&gt;Render the window&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Steps 1-4 happened every single time, even though the executable and most of the state never changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Forking Wlines
&lt;/h2&gt;

&lt;p&gt;The solution was simple in concept: don't restart wlines every time. Keep it running as a background process (like a unix daemon) and communicate with it via named pipes. Instead of spawning a subprocess, windmenu opens a connection to &lt;code&gt;\\.\pipe\wlines_pipe&lt;/code&gt; and sends the menu items directly to the already-initialized wlines daemon:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Current daemon-based approach (simplified)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;pipe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;OpenFileW&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;to_wide_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;pipe&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;wlines_pipe"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.as_ptr&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;GENERIC_WRITE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// ... flags&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;WriteFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="nf"&gt;.as_bytes&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The daemon is already running, already has its window hidden and ready, already has fonts loaded. When the command arrives, it just shows the window and renders the items.&lt;/p&gt;

&lt;p&gt;This approach required forking wlines. The original was designed as a one-shot command-line tool: like dmenu, it configured everything via command-line arguments. I extended it to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run as a background process&lt;/li&gt;
&lt;li&gt;Listen on a named pipe for commands&lt;/li&gt;
&lt;li&gt;Keep the window hidden until needed&lt;/li&gt;
&lt;li&gt;Read configuration from a text file&lt;/li&gt;
&lt;li&gt;Support vim keybindings (&lt;code&gt;hjkl&lt;/code&gt; navigation)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fork lives at &lt;a href="https://github.com/gicrisf/wlines" rel="noopener noreferrer"&gt;github.com/gicrisf/wlines&lt;/a&gt;. Still pure C, which makes sense for this kind of low-level UI work.&lt;/p&gt;

&lt;p&gt;My fork can compile as either a daemon or a traditional one-shot binary. Windmenu supports both modes: it prefers the daemon method (faster), but if the daemon isn't running and you've configured &lt;code&gt;wlines_cli_path&lt;/code&gt; in &lt;code&gt;windmenu.toml&lt;/code&gt;, it falls back to the subprocess approach. Use the fast daemon setup normally, keep the traditional method as fallback, or use it exclusively if you prefer. The architecture doesn't force you into one approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two-Daemon Architecture
&lt;/h2&gt;

&lt;p&gt;The two-daemon architecture wasn't just about performance. It was about preserving dmenu's fundamental philosophy: a generic, reusable selector interface. On Linux, dmenu is not locked to launching applications. You can pipe anything to it: password manager entries, file search results, window manager commands, music playlists. It's a moldable interface that displays input and returns selection, while bash scripts handle the actual logic.&lt;/p&gt;

&lt;p&gt;On Windows, this approach isn't as straightforward. You could pair AutoHotkey with PowerShell scripts, but AutoHotkey requires administrator permissions for certain operations. Since I needed an active process to capture hotkeys anyway, it made sense to build something more integrated: a dedicated daemon that handles application discovery, configuration, and custom commands.&lt;/p&gt;

&lt;p&gt;The split:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;wlines-daemon.exe&lt;/strong&gt;: Generic selector&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Displays items, handles navigation&lt;/li&gt;
&lt;li&gt;Returns selection&lt;/li&gt;
&lt;li&gt;Doesn't care what you're selecting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;windmenu.exe&lt;/strong&gt;: Application launcher (one use case)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monitors for hotkeys&lt;/li&gt;
&lt;li&gt;Discovers applications (Start Menu, Windows Store apps)&lt;/li&gt;
&lt;li&gt;Sends program list to wlines-daemon&lt;/li&gt;
&lt;li&gt;Executes the selected program&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They communicate via named pipes (&lt;code&gt;\\.\pipe\wlines_pipe&lt;/code&gt;). External scripts can still access wlines directly, but windmenu makes it easier: bind PowerShell scripts to custom commands in the TOML config. For example, you can run WSL commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# Execute commands in WSL&lt;/span&gt;
&lt;span class="nn"&gt;[[commands]]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"WSL: Update packages"&lt;/span&gt;
&lt;span class="py"&gt;args&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"wsl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"-e"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"bash"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"-c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"sudo apt update &amp;amp;&amp;amp; sudo apt upgrade"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c"&gt;# Open WSL home directory&lt;/span&gt;
&lt;span class="nn"&gt;[[commands]]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"WSL Home"&lt;/span&gt;
&lt;span class="py"&gt;args&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"wsl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"-e"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"bash"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"-c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"cd ~ &amp;amp;&amp;amp; exec bash"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...or execute PowerShell snippets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run a beep using PowerShell&lt;/span&gt;
&lt;span class="nn"&gt;[[commands]]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Beep"&lt;/span&gt;
&lt;span class="py"&gt;args&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"powershell"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"-Command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"[Console]::Beep(440, 500)"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Managing both daemons required a unified interface. I built a &lt;code&gt;Daemon&lt;/code&gt; trait with separate implementations for &lt;code&gt;WindmenuDaemon&lt;/code&gt; and &lt;code&gt;WlinesDaemon&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;trait&lt;/span&gt; &lt;span class="n"&gt;Daemon&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_process_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;DaemonError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;DaemonError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;DaemonError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;DaemonStatus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// Startup method management...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This trait-based approach made the CLI natural to implement: &lt;code&gt;windmenu daemon all restart&lt;/code&gt; controls both, &lt;code&gt;windmenu daemon wlines restart&lt;/code&gt; controls just wlines. The code mirrors the command structure (which was deliberate, because I modeled the entire interface after systemd).&lt;/p&gt;

&lt;h2&gt;
  
  
  Systemd-Inspired CLI
&lt;/h2&gt;

&lt;p&gt;I needed to manage two daemons (windmenu and wlines) with complete lifecycle control: start, stop, restart, status checking, startup configuration. Windows Services require administrator privileges to install and manage, making deployment friction-heavy. I wanted Windmenu to work in restricted corporate environments where users can't install system services.&lt;/p&gt;

&lt;p&gt;So I built a daemon manager into the CLI itself, taking inspiration from the best daemon manager I knew: systemd.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;windmenu daemon self start
windmenu daemon self status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pattern is consistent: &lt;code&gt;windmenu daemon &amp;lt;target&amp;gt; &amp;lt;action&amp;gt;&lt;/code&gt;. The target can be &lt;code&gt;self&lt;/code&gt; (windmenu daemon), &lt;code&gt;wlines&lt;/code&gt; (wlines daemon), or &lt;code&gt;all&lt;/code&gt; (both daemons). The actions mirror systemd's verbs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start both daemons&lt;/span&gt;
windmenu daemon all start

&lt;span class="c"&gt;# Restart just wlines&lt;/span&gt;
windmenu daemon wlines restart

&lt;span class="c"&gt;# Check status of everything&lt;/span&gt;
windmenu daemon all status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This CLI design made the code architecture cleaner too. The &lt;code&gt;Daemon&lt;/code&gt; trait I mentioned earlier emerged directly from thinking about what operations systemd exposes. If systemd needs &lt;code&gt;start&lt;/code&gt;, &lt;code&gt;stop&lt;/code&gt;, &lt;code&gt;restart&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;, and &lt;code&gt;enable&lt;/code&gt;/&lt;code&gt;disable&lt;/code&gt; for any service, then my &lt;code&gt;Daemon&lt;/code&gt; trait should provide the same operations for any daemon.&lt;/p&gt;

&lt;p&gt;The implementation uses Clap's derive macros for argument parsing, which generates help text automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[derive(Parser)]&lt;/span&gt;
&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;Commands&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Daemon&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;#[command(subcommand)]&lt;/span&gt;
        &lt;span class="n"&gt;daemon_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DaemonType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;Test&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;#[command(subcommand)]&lt;/span&gt;
        &lt;span class="n"&gt;test_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TestType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;Fetch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;#[command(subcommand)]&lt;/span&gt;
        &lt;span class="n"&gt;fetch_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FetchType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Subcommand)]&lt;/span&gt;
&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;DaemonType&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Self_&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nd"&gt;#[command(subcommand)]&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DaemonAction&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;Wlines&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nd"&gt;#[command(subcommand)]&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DaemonAction&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;All&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nd"&gt;#[command(subcommand)]&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DaemonAction&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Subcommand)]&lt;/span&gt;
&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;DaemonAction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Stop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Restart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Enable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;StartupMethod&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;Disable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;StartupMethod&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This declarative approach keeps the CLI interface consistent with minimal boilerplate. The structure in the code mirrors the structure users type at the command line.&lt;/p&gt;

&lt;p&gt;The consistency makes the tool predictable. If you've used systemd, the daemon management commands feel familiar. If you haven't, the hierarchical structure (&lt;code&gt;windmenu &amp;lt;category&amp;gt; &amp;lt;target&amp;gt; &amp;lt;action&amp;gt;&lt;/code&gt;) makes the available operations discoverable through tab completion and &lt;code&gt;--help&lt;/code&gt; flags.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reparse Points
&lt;/h2&gt;

&lt;p&gt;The launcher needs to find applications before it can launch them. Windows Store apps presented an unexpected challenge: unlike classic Win32 applications that live in &lt;code&gt;Program Files&lt;/code&gt;, modern Windows apps hide in &lt;code&gt;%LOCALAPPDATA%\Microsoft\WindowsApps&lt;/code&gt; as reparse points (symbolic links with extra metadata).&lt;/p&gt;

&lt;p&gt;The naive approach (treating them like regular shortcuts) failed immediately. These aren't &lt;code&gt;.lnk&lt;/code&gt; files; they're special filesystem objects that Windows handles differently. The solution required detecting reparse points using &lt;code&gt;GetFileAttributesW&lt;/code&gt; and the &lt;code&gt;FILE_ATTRIBUTE_REPARSE_POINT&lt;/code&gt; flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;is_reparse_point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;wide_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;to_wide_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GetFileAttributesW&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wide_path&lt;/span&gt;&lt;span class="nf"&gt;.as_ptr&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;


        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;INVALID_FILE_ATTRIBUTES&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;FILE_ATTRIBUTE_REPARSE_POINT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets Windmenu find modern apps like Windows Terminal, Calculator, and Settings alongside traditional desktop applications. The scanning happens once at startup, keeping the hotkey response instant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Themes
&lt;/h2&gt;

&lt;p&gt;Wlines originally configured everything via command-line arguments, like dmenu. For daemon mode, I added a text configuration file so wlines wouldn't parse dozens of flags on every invocation.&lt;/p&gt;

&lt;p&gt;This meant having to maintain two config files: &lt;code&gt;windmenu.toml&lt;/code&gt; for the launcher, &lt;code&gt;wlines-config.txt&lt;/code&gt; for the UI. Different formats (verbose TOML for Rust, concise text for C), different field names. Users would need to understand both and remember which settings go where.&lt;/p&gt;

&lt;p&gt;I solved this by auto-generating &lt;code&gt;wlines-config.txt&lt;/code&gt; from a &lt;code&gt;[theme]&lt;/code&gt; section in &lt;code&gt;windmenu.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[theme]&lt;/span&gt;
&lt;span class="py"&gt;lines&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;
&lt;span class="py"&gt;padding_x&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
&lt;span class="py"&gt;width_x&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
&lt;span class="py"&gt;background_color&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"#1e1e2e"&lt;/span&gt;
&lt;span class="py"&gt;foreground_color&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"#cdd6f4"&lt;/span&gt;
&lt;span class="py"&gt;selected_background_color&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"#89b4fa"&lt;/span&gt;
&lt;span class="py"&gt;selected_foreground_color&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"#1e1e2e"&lt;/span&gt;
&lt;span class="py"&gt;text_background_color&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"#313244"&lt;/span&gt;
&lt;span class="py"&gt;text_foreground_color&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"#cdd6f4"&lt;/span&gt;
&lt;span class="py"&gt;font_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;theme.rs&lt;/code&gt; module translates these descriptive field names into wlines's format and generates &lt;code&gt;wlines-config.txt&lt;/code&gt; on startup. Windmenu's config re-implements all of wlines's configuration fields. No reason to manually edit the generated file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc9ml7v8pme8hvp06ipq9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc9ml7v8pme8hvp06ipq9.png" alt="Windmenu using the Catppuccin Mocha theme configured above" width="800" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Themes become shareable: a &lt;code&gt;[theme]&lt;/code&gt; section is just text. Copy someone's theme into your &lt;code&gt;windmenu.toml&lt;/code&gt;, restart the daemon. Done.&lt;/p&gt;

&lt;p&gt;Wlines stays independent. It's a standalone component with its own config file that doesn't need windmenu. Use it with your own scripts for password management or file selection. The auto-generation is windmenu's convenience layer, not a constraint on wlines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Static Linking
&lt;/h2&gt;

&lt;p&gt;Initially, I compiled with Rust's MSVC toolchain on Windows. The binaries ran fine, but they required the Visual C++ redistributables (&lt;code&gt;vcruntime140.dll&lt;/code&gt;, &lt;code&gt;api-ms-win-crt-*.dll&lt;/code&gt;). I accepted this limit at first (most Windows machines have these installed anyway).&lt;/p&gt;

&lt;p&gt;Later in, while I was developing the NSIS installer (we'll see right after), I couldn't tolerate it. &lt;strong&gt;I wanted binaries that work everywhere&lt;/strong&gt;: copy &lt;code&gt;windmenu.exe&lt;/code&gt; to any Windows 10/11 machine and run it. No redistributables, no runtime dependencies, no installation friction.&lt;/p&gt;

&lt;p&gt;Rust's MSVC toolchain uses Microsoft's linker (&lt;code&gt;link.exe&lt;/code&gt;), which defaults to dynamic linking for compatibility. The GNU toolchain (mingw) avoids this by using &lt;code&gt;gcc&lt;/code&gt; and static linking by default. Building natively on Windows with the GNU toolchain still pulled in Microsoft CRT dependencies. Cross-compiling from Linux to Windows with mingw-w64 produced cleaner results. The &lt;code&gt;.cargo/config.toml&lt;/code&gt; forces static linking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[target.x86_64-pc-windows-gnu]&lt;/span&gt;
&lt;span class="py"&gt;linker&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"x86_64-w64-mingw32-gcc"&lt;/span&gt;
&lt;span class="py"&gt;rustflags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s"&gt;"-C"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;"panic&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;abort&lt;/span&gt;&lt;span class="s"&gt;",&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;    &lt;span class="s"&gt;"-C"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;"link-args&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;-static&lt;/span&gt;&lt;span class="s"&gt;",&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;    &lt;span class="s"&gt;"-C"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;"link-args&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;-Wl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;-Bstatic&lt;/span&gt;&lt;span class="s"&gt;",&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;    &lt;span class="s"&gt;"-C"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;"link-args&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;-l:libmsvcrt.a&lt;/span&gt;&lt;span class="s"&gt;",&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;    &lt;span class="s"&gt;"-C"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;"link-args&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;-l:libucrt.a&lt;/span&gt;&lt;span class="s"&gt;",&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;    &lt;span class="s"&gt;"-C"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;"link-args&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;-l:libpthread.a&lt;/span&gt;&lt;span class="s"&gt;",&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;    &lt;span class="s"&gt;"-C"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;"link-args&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;-l:libgcc.a&lt;/span&gt;&lt;span class="s"&gt;",&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;    &lt;span class="s"&gt;"-C"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;"link-args&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;-nostartfiles&lt;/span&gt;&lt;span class="s"&gt;",&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;    &lt;span class="s"&gt;"-C"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;"link-args&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;-Wl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;--gc-sections&lt;/span&gt;&lt;span class="s"&gt;",&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;    &lt;span class="s"&gt;"-C"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;"link-args&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;-Wl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;--as-needed&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-l:lib*.a&lt;/code&gt; syntax forces static linking of specific libraries. The explicit target means Cargo places output in &lt;code&gt;target/x86_64-pc-windows-gnu/release/&lt;/code&gt; instead of &lt;code&gt;target/release/&lt;/code&gt;. Dead code elimination (&lt;code&gt;--gc-sections&lt;/code&gt;) and selective linking (&lt;code&gt;--as-needed&lt;/code&gt;) reduce binary size.&lt;/p&gt;

&lt;p&gt;Verifying dependencies with &lt;code&gt;dumpbin /DEPENDENTS&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Microsoft (R) COFF/PE Dumper Version 14.42.34435.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file target\x86_64-pc-windows-gnu\release\windmenu.exe

File Type: EXECUTABLE IMAGE

  Image has the following dependencies:

    KERNEL32.dll
    SHELL32.dll
    USER32.dll
    api-ms-win-core-synch-l1-2-0.dll
    bcryptprimitives.dll
    msvcrt.dll
    ntdll.dll
    USERENV.dll
    WS2_32.dll
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These dependencies could be reduced further, but I don't expect people to (really) run this from Windows 3.1, and I'm not willing to give up Rust's threading primitives. This compromise works: system DLLs present on all Windows 10+ machines, no redistributables required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;The project includes NSIS installer scripts with automated build tools for both PowerShell and Bash. The scripts handle dependency downloads (fetching &lt;code&gt;wlines-daemon.exe&lt;/code&gt; from releases), Rust compilation, and packaging:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;\build-installer.ps1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Windows startup methods are fragmented, and each has different privilege requirements and failure modes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Privilege Required&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Registry&lt;/td&gt;
&lt;td&gt;user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Task Scheduler&lt;/td&gt;
&lt;td&gt;user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Startup folder (User)&lt;/td&gt;
&lt;td&gt;user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Startup folder (All users)&lt;/td&gt;
&lt;td&gt;admin&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Group Policy might block Task Scheduler but leave registry keys open. IT policies might lock down the registry but allow startup folder scripts. Some systems restrict everything except one method. Four options mean at least one works (hopefully).&lt;/p&gt;

&lt;p&gt;The graphical installer covers the basics: binaries, dependencies, startup method options, uninstall support, and Start Menu shortcuts. It cleans up all startup methods on uninstall while preserving user-edited configuration files.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fum7nf9gk7e8lpxqn2wkb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fum7nf9gk7e8lpxqn2wkb.png" alt="windmenu nsis" width="800" height="694"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For users who prefer manual control, windmenu's CLI offers the same functionality:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;windmenu daemon all &lt;span class="nb"&gt;enable &lt;/span&gt;registry
windmenu daemon all &lt;span class="nb"&gt;enable &lt;/span&gt;task
windmenu daemon wlines &lt;span class="nb"&gt;enable &lt;/span&gt;user-folder
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The NSIS installer provides a graphical interface for these operations, while the CLI remains available for scripting and power users.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;The current version of Windmenu is stable and daily-driveable. It's fast, configurable, and stays out of the way.&lt;/p&gt;

&lt;p&gt;Some features I'm considering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Frecency-based sorting&lt;/strong&gt;: Frequently and recently used apps should float to the top&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;WiFi network switching&lt;/strong&gt;: At some point during development, I thought "What if I could control WiFi networks from the launcher?" This led to implementing the &lt;code&gt;wlan.rs&lt;/code&gt; module using Windows Native WiFi API. It wraps unsafe FFI calls in a safe Rust API with proper RAII cleanup via the &lt;code&gt;Drop&lt;/code&gt; trait. Right now it supports forcing WiFi scans (useful for refreshing available networks), but what I really want is full network management (selecting and connecting to specific networks directly from Windmenu without touching the system tray). The infrastructure is there, waiting for the connection management pieces to be wired up.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fast file search&lt;/strong&gt;: Using ripgrep, maybe, avoiding the bloat of Windows indexing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;System tray control&lt;/strong&gt;: Managing visibility and lifecycle of system tray applications&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the core seems solid (on my machine, anyway). It does what I needed: provides a dmenu-like experience on Windows without compromise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;Building Windmenu scratched a personal itch while exploring Windows system programming in Rust. What started as "I want dmenu on Windows" became a deep dive into Windows internals, daemon architecture, and building distributable system tools.&lt;/p&gt;

&lt;p&gt;This project has been a learning journey, particularly around Win32 APIs, process management, and Rust's concurrency primitives on Windows. I'm still learning the nuances of Windows system programming, so if you spot areas for improvement or have suggestions about better approaches, I'd genuinely appreciate the feedback.&lt;/p&gt;

&lt;p&gt;If you're tired of Windows start menu, give Windmenu a try. It's fast, minimal, and keyboard-first.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Windmenu is available on &lt;a href="https://github.com/gicrisf/windmenu" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. The installer includes everything you need to get started. If you find it useful, contributions and feedback are always welcome.&lt;/em&gt;&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;There's also &lt;a href="https://www.windowslatest.com/2025/10/19/microsoft-cant-fix-windows-11-search-so-its-handing-it-to-ask-copilot-on-the-taskbar/" rel="noopener noreferrer"&gt;Copilot integration in the search&lt;/a&gt; now. Another AI layer sitting between you and your programs, analyzing search behavior to suggest actions. The taskbar search is being replaced with "Ask Copilot" - more processing, more latency, more things you didn't ask for. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;Of course, ideas flourished while I was working on it and now I do many other things with it, but this illustrates the core need. (We'll talk about how I use Emacs on Windows another time, I don't want to go off topic). ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>microsoft</category>
      <category>rust</category>
      <category>c</category>
    </item>
    <item>
      <title>Kaomel: a snappy kaomoji picker for Emacs</title>
      <dc:creator>Giovanni Crisalfi</dc:creator>
      <pubDate>Wed, 13 Aug 2025 18:01:31 +0000</pubDate>
      <link>https://forem.com/gicrisf/kaomel-a-snappy-kaomoji-picker-for-emacs-7j2</link>
      <guid>https://forem.com/gicrisf/kaomel-a-snappy-kaomoji-picker-for-emacs-7j2</guid>
      <description>&lt;p&gt;I always liked kaomojis, but I never liked using the mouse to pick an emoji of any kind. Actually, I just don't like using the mouse. So I thought I could access the kaomoji world through Emacs keymagic. What started as "wouldn't it be nice to have a kaomoji picker in Emacs?" became a journey that would consume more weekends than initially planned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Origins
&lt;/h2&gt;

&lt;p&gt;The idea struck me in July 2023, but considering Emacs's long history, I knew someone had probably built something like this already. Sure enough, I found &lt;a href="https://github.com/kuanyui/kaomoji.el" rel="noopener noreferrer"&gt;kaomoji.el&lt;/a&gt;, but its dataset was much smaller than my local collection, and it required Helm while I was using Vertico.&lt;/p&gt;

&lt;p&gt;I could have forked it, but that would mean rewriting the interface layer and adapting to their dataset structure: basically everything. So I chose to design from scratch.&lt;/p&gt;

&lt;p&gt;In my mind, the real goal was bigger: plug my dataset into any input interface. Why not &lt;strong&gt;dmenu&lt;/strong&gt;? Why not &lt;a href="https://github.com/gicrisf/windmenu" rel="noopener noreferrer"&gt;windmenu&lt;/a&gt;? (I'm working on it.) Why not the CLI? I want my kaomojis everywhere. Sure, I prefer staying in Emacs, but we all know sometimes you're forced to venture outside your parentheses-wrapped sanctuary.&lt;/p&gt;

&lt;p&gt;Before diving into implementation, I had to establish the non-negotiables. Two constraints would drive every subsequent decision: &lt;strong&gt;interface flexibility&lt;/strong&gt; and &lt;strong&gt;dataset extensibility&lt;/strong&gt;. These weren't features you could retrofit; they were architectural decisions that needed to be right from the start.&lt;/p&gt;

&lt;p&gt;The first concept seemed simple enough: parse a JSON dataset of kaomojis, present them through a fuzzy-searchable interface, then insert the selection at point or copy to clipboard. These problems could have adapted perfectly to any textual interface.&lt;/p&gt;

&lt;p&gt;But as I dove into the implementation, what seemed like a weekend project revealed layers of interesting problems that would define the entire architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Completion flexibility&lt;/em&gt; meant the package had to work equally well with Vertico, Helm, or any future completion framework. This wasn't about supporting every possible interface, it was about designing abstractions that didn't lock users (and developers) into specific tools.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Dataset extensibility&lt;/em&gt; meant the structure had to accommodate languages I hadn't thought of yet. Today it's Japanese, English and Italian; tomorrow it might need Arabic script or emoji modifiers. The data model needed to be future-proof.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Dataset
&lt;/h2&gt;

&lt;p&gt;My first challenge was the data itself. I looked around for existing kaomoji datasets, hoping someone had already solved this problem. What I found was a constellation of different approaches. Projects like &lt;a href="https://github.com/fand/kao" rel="noopener noreferrer"&gt;kao&lt;/a&gt; used structured text files that were clean and human-readable, serving their Japanese-focused community well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@?          （？〜？         顔文字
@あ?        (๑•ૅㅁ•๑)ぁ？    顔文字
@あめ       ⋰⋰ ☂ (ృ ˘ ꒳ ˘  ృ 　)ु  ⋱⋱    顔文字
@ありがとう  (*ゝω・)ﾉ ｱﾘｶﾞ㌧♪    顔文字
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This wouldn't work for my multilingual ambitions (someone searching for "thank you" or "grazie" would find nothing) but I appreciated the clarity of the tab-separated format.&lt;/p&gt;

&lt;p&gt;The dataset that came closest to my vision was from the &lt;a href="https://github.com/Coiven/kaomoji-vscode" rel="noopener noreferrer"&gt;kaomoji-vscode&lt;/a&gt; extension. It had the structured JSON I wanted, with tags that could support search.&lt;/p&gt;

&lt;p&gt;The original extension was designed to randomly pick one kaomoji from the array when a user selected a tag. This made sense for their use case—users would get variety without having to choose between dozens of similar emoticons. But what caught my attention wasn't the random selection; it was how the structure kept the dataset maintainable and compact by grouping related kaomojis under semantic tags.&lt;/p&gt;

&lt;p&gt;From the beginning, I knew the JSON would just be a source format, a way to organize and maintain the data that I'd transform into an optimized Emacs Lisp vector at runtime. This pre-processing approach gave me the freedom to make the dataset more compact and maintainable without being constrained by the original structure in the Lisp realm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"laugh 笑 哈哈"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"yan"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"o(*≧▽≦)ツ┏━┓"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"(/≥▽≤/)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ヾ(o◕∀◕)ﾉ"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But even this felt limited: tags weren't atomic; instead, they contained multiple languages and concepts in a single string. The "yan" field (containing the actual kaomojis) was an array, meaning each tag mapped to multiple emoticons. Having multiple kaomojis per semantic tag actually made sense: why should "laugh" map to just one emoticon when there are dozens of ways to express laughter?&lt;/p&gt;

&lt;p&gt;The original VSCode extension would randomly select one kaomoji from this array when users picked a tag, adding an element of surprise and preventing the interface from becoming overwhelming. But I wanted users to see and choose from all available options (the whole point was having that rich, searchable collection at your fingertips).&lt;/p&gt;

&lt;p&gt;This led me to think about the final Emacs Lisp structure: a vector where every kaomoji would have its full context available for instant search. In Emacs, users expect instant fuzzy matching across everything. That meant I needed a flat, searchable structure. The redundancy would be worth the maintainability.&lt;/p&gt;

&lt;p&gt;Here's what a single processed entry looks like in the Emacs Lisp data structure (hash-table metadata stripped for clarity):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="sx"&gt;#s&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;hash-table&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt; 
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tag"&lt;/span&gt; &lt;span class="nv"&gt;[&lt;/span&gt;
    &lt;span class="sx"&gt;#s&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;hash-table&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orig"&lt;/span&gt; &lt;span class="s"&gt;"laugh "&lt;/span&gt; &lt;span class="s"&gt;"hira"&lt;/span&gt; &lt;span class="s"&gt;"laugh "&lt;/span&gt; &lt;span class="s"&gt;"kana"&lt;/span&gt; &lt;span class="s"&gt;"laugh "&lt;/span&gt; 
                        &lt;span class="s"&gt;"hepburn"&lt;/span&gt; &lt;span class="s"&gt;"laugh "&lt;/span&gt; &lt;span class="s"&gt;"kunrei"&lt;/span&gt; &lt;span class="s"&gt;"laugh "&lt;/span&gt; &lt;span class="s"&gt;"passport"&lt;/span&gt; &lt;span class="s"&gt;"laugh "&lt;/span&gt;
                        &lt;span class="s"&gt;"en"&lt;/span&gt; &lt;span class="s"&gt;"laugh "&lt;/span&gt; &lt;span class="s"&gt;"it"&lt;/span&gt; &lt;span class="s"&gt;"ridere"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="sx"&gt;#s&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;hash-table&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orig"&lt;/span&gt; &lt;span class="s"&gt;"笑"&lt;/span&gt; &lt;span class="s"&gt;"hira"&lt;/span&gt; &lt;span class="s"&gt;"わらい"&lt;/span&gt; &lt;span class="s"&gt;"kana"&lt;/span&gt; &lt;span class="s"&gt;"ワライ"&lt;/span&gt; 
                        &lt;span class="s"&gt;"hepburn"&lt;/span&gt; &lt;span class="s"&gt;"warai"&lt;/span&gt; &lt;span class="s"&gt;"kunrei"&lt;/span&gt; &lt;span class="s"&gt;"warai"&lt;/span&gt; &lt;span class="s"&gt;"passport"&lt;/span&gt; &lt;span class="s"&gt;"warai"&lt;/span&gt;
                        &lt;span class="s"&gt;"en"&lt;/span&gt; &lt;span class="s"&gt;"笑"&lt;/span&gt; &lt;span class="s"&gt;"it"&lt;/span&gt; &lt;span class="s"&gt;"笑"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="sx"&gt;#s&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;hash-table&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orig"&lt;/span&gt; &lt;span class="s"&gt;"哈"&lt;/span&gt; &lt;span class="s"&gt;"hira"&lt;/span&gt; &lt;span class="s"&gt;"ごう"&lt;/span&gt; &lt;span class="s"&gt;"kana"&lt;/span&gt; &lt;span class="s"&gt;"ゴウ"&lt;/span&gt; 
                        &lt;span class="s"&gt;"hepburn"&lt;/span&gt; &lt;span class="s"&gt;"gou"&lt;/span&gt; &lt;span class="s"&gt;"kunrei"&lt;/span&gt; &lt;span class="s"&gt;"gou"&lt;/span&gt; &lt;span class="s"&gt;"passport"&lt;/span&gt; &lt;span class="s"&gt;"gou"&lt;/span&gt;
                        &lt;span class="s"&gt;"en"&lt;/span&gt; &lt;span class="s"&gt;"哈"&lt;/span&gt; &lt;span class="s"&gt;"it"&lt;/span&gt; &lt;span class="s"&gt;"哈"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="nv"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"yan"&lt;/span&gt; &lt;span class="nv"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"o(*≧▽≦)ツ┏━┓"&lt;/span&gt; &lt;span class="s"&gt;"(/≥▽≤/)"&lt;/span&gt; &lt;span class="s"&gt;"ヾ(o◕∀◕)ﾉ"&lt;/span&gt;&lt;span class="nv"&gt;]&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The kaomoji got tagged not just with "laugh" in English, but with わらい in hiragana, ワライ in katakana, and "ridere" in Italian. &lt;/p&gt;

&lt;p&gt;This dataset structure made it trivial to add new languages later. Want French support? The mapping system is already there - just add "rire" to the semantic space alongside "laugh" and "わらい". The beauty is that users can mix and match these language preferences without any structural changes to the core data.&lt;/p&gt;

&lt;p&gt;This multilingual-first approach became crucial when I realized different users would want different tag languages displayed. Some prefer English for readability, others want the original Japanese for authenticity, and some want romanization for typing efficiency. This new model had the proper flexibility from the beginning, making it straightforward to support all these preferences simultaneously.&lt;/p&gt;

&lt;h2&gt;
  
  
  Romanization
&lt;/h2&gt;

&lt;p&gt;Early in development, I got excited about romanization. Why not convert "笑" to "warai" automatically, making the dataset searchable by romaji? My first approach was to integrate Python bindings for the &lt;a href="https://pypi.org/project/romkan/" rel="noopener noreferrer"&gt;Romkan package&lt;/a&gt; directly into the runtime vector build process: every time kaomel loaded, it would generate romanized versions on the fly.&lt;/p&gt;

&lt;p&gt;But this felt heavy. Why introduce Python dependencies when I could handle romanization in pure Emacs Lisp? This led me to create &lt;a href="https://github.com/gicrisf/romkan.el/" rel="noopener noreferrer"&gt;romkan.el&lt;/a&gt;, my own package for Japanese-romaji conversion that worked entirely within Emacs.&lt;/p&gt;

&lt;p&gt;Even after building a working Emacs Lisp solution, I realized there was an even cleaner approach. Instead of adding romkan as a runtime dependency (requiring users to install another package) I used it as a development tool. I ran the conversions once during dataset preparation, then baked the romanized results directly into the static dataset. This kept kaomel completely dependency-free while still providing full romanization support.&lt;/p&gt;

&lt;p&gt;At this point, could I have done it all with the Python Romkan? Yes. Do I care? No.&lt;/p&gt;

&lt;h2&gt;
  
  
  Completion flexibility
&lt;/h2&gt;

&lt;p&gt;The Emacs ecosystem has been shifting toward &lt;code&gt;completing-read&lt;/code&gt; and frameworks like Vertico, but many packages still assume Helm as the primary interface. Helm, while powerful (and honestly beautiful), is essentially a parallel universe with its own conventions and complexity.&lt;/p&gt;

&lt;p&gt;I decided to embrace the modern direction both because I think it represents the cleaner architectural path (and, as I said before, because I was using Vertico myself), while still supporting both approaches.&lt;/p&gt;

&lt;p&gt;So I built kaomel with automatic Helm detection when available. The package picks Helm if it finds it, but users can force &lt;code&gt;completing-read&lt;/code&gt; via &lt;code&gt;kaomel-avoid-helm&lt;/code&gt;. This way, whether you're team Vertico or team Helm, kaomel works seamlessly with your preferred completion framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  Text Alignment
&lt;/h2&gt;

&lt;p&gt;One detail that consumed far more time than expected: text alignment. When you're displaying both English tags and kaomojis, string length calculations become tricky. The tag "laugh 笑" and the kaomoji "(/≥▽≤/)" don't behave the same way in terms of display width.&lt;/p&gt;

&lt;p&gt;Emacs provides &lt;code&gt;string-width&lt;/code&gt; for display-aware measurements, which was the key to solving this. Instead of naive character counting, I used &lt;code&gt;string-width&lt;/code&gt; to calculate actual display width, then padded everything to achieve consistent alignment. Whether you're looking at "laugh 笑" or "ಠ_ಠ disapproval", the spacing stays visually consistent. These details matter when you're building something people use dozens of times per day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;The configuration system turned out to be more important than I initially expected. I tried to provide a high degree of customization while building what I thought were sensible defaults. Instead of hard-coding interface choices, I made the key aspects customizable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which completion framework to use (&lt;code&gt;kaomel-force-completing-read&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Which tag languages to display (&lt;code&gt;kaomel-tag-langs&lt;/code&gt;) - supporting original, hiragana, katakana, English, Italian, and romanization formats&lt;/li&gt;
&lt;li&gt;Whether to filter ASCII-only tags (&lt;code&gt;kaomel-only-ascii-tags&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;How aggressive tag trimming should be (&lt;code&gt;kaomel-heavy-trim-tags&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Visual formatting options like separators (&lt;code&gt;kaomel-tag-val-separator&lt;/code&gt;, &lt;code&gt;kaomel-tag-tag-separator&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Custom prompts (&lt;code&gt;kaomel-prompt&lt;/code&gt;) and candidate limits (&lt;code&gt;kaomel-candidate-number-limit&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was user-friendly design that happened to encourage clean abstractions. By making these choices configurable, I had to ensure the code worked correctly regardless of user preferences, which led to a more robust implementation.&lt;/p&gt;

&lt;p&gt;For detailed documentation of each configuration option, see the Configuration section in the &lt;a href="https://github.com/gicrisf/kaomel" rel="noopener noreferrer"&gt;README&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Literate Programming
&lt;/h2&gt;

&lt;p&gt;The early development followed a literate programming approach, with all exploration happening in an org-mode document mixing prose, code blocks, and results. This wasn't just note-taking, it was thinking through code, documenting each decision as it happened.&lt;/p&gt;

&lt;p&gt;Each code block could be evaluated in place, with results appearing inline. Want to test JSON parsing? Write a block, execute it, see the output. Need to try different data structures? Compare approaches side-by-side with immediate feedback. This resembles the classic Lisp &lt;strong&gt;REPL&lt;/strong&gt; workflow but with all the organizational benefits of &lt;a href="https://orgmode.org/" rel="noopener noreferrer"&gt;org-mode&lt;/a&gt;. The development document became a living laboratory.&lt;/p&gt;

&lt;p&gt;This approach proved invaluable for a project involving data transformations. Instead of jumping between files, everything lived in one navigable document. The prose explained the reasoning, the code showed the implementation, and the results validated the approach.&lt;/p&gt;

&lt;p&gt;Developing in Emacs Lisp has a unique rhythm, and literate programming amplifies it. The edit-eval-test cycle is instant, encouraging experimental development. When I could test a search query instantly, I noticed performance issues early. When I could try different tag arrangements in real-time, I found the most readable format quickly. The org document is a thinking space where ideas can be explored, tested, refined, and documented simultaneously.&lt;/p&gt;

&lt;p&gt;What's interesting is how this approach naturally captured feature evolution (which led to this blog post). &lt;/p&gt;

&lt;h2&gt;
  
  
  Tooling
&lt;/h2&gt;

&lt;p&gt;As the lisp snippets crystallized into a proper package, I found myself needing tools that could work outside my personal Emacs environment.&lt;/p&gt;

&lt;p&gt;I'm traditionally a shell person when developing anything, and I like keeping that solid shell alternative in Emacs development too. No surprise I'm a &lt;a href="https://github.com/doomemacs/doomemacs" rel="noopener noreferrer"&gt;Doom Emacs&lt;/a&gt; admirer: there's something deeply satisfying about typing &lt;code&gt;./doom doctor&lt;/code&gt; and watching it methodically check your entire configuration, so I initially adopted that approach for kaomel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# I used it to regenerate kaomel-data.el&lt;/span&gt;
&lt;span class="c"&gt;# It just wrapped the `kaomel-dev-regenerate-data` batch call&lt;/span&gt;
./kaomel-data-gen.sh gen
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But I further simplified this by embracing eldev fully. Eldev has &lt;a href="https://emacs-eldev.github.io/eldev/#custom-builders" rel="noopener noreferrer"&gt;builders&lt;/a&gt; specifically designed for generating files from sources, and I always prefer writing Lisp instead of bash wrappers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;eldev-defbuilder&lt;/span&gt; &lt;span class="nv"&gt;kaomel-data-generator&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;source&lt;/span&gt; &lt;span class="nv"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="ss"&gt;:short-name&lt;/span&gt;     &lt;span class="s"&gt;"data"&lt;/span&gt;
  &lt;span class="ss"&gt;:source-files&lt;/span&gt;   &lt;span class="s"&gt;"kaomel-data.json"&lt;/span&gt;
  &lt;span class="ss"&gt;:targets&lt;/span&gt;        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kaomel-data.json"&lt;/span&gt; &lt;span class="nv"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"kaomel-data.el"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="ss"&gt;:collect&lt;/span&gt;        &lt;span class="s"&gt;":default"&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;load-file&lt;/span&gt; &lt;span class="s"&gt;"kaomel-utils.el"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;kaomel-dev--generate-data-file&lt;/span&gt; &lt;span class="nv"&gt;source&lt;/span&gt; &lt;span class="nv"&gt;target&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This integrates perfectly with eldev's build system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eldev build          &lt;span class="c"&gt;# Generates kaomel-data.el if kaomel-data.json changed&lt;/span&gt;
eldev &lt;span class="nb"&gt;test&lt;/span&gt;           &lt;span class="c"&gt;# Run tests&lt;/span&gt;
eldev lint           &lt;span class="c"&gt;# Run linter&lt;/span&gt;
eldev compile        &lt;span class="c"&gt;# Compile to bytecode&lt;/span&gt;
eldev clean data     &lt;span class="c"&gt;# Clean generated files&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach comes in handy for CI/CD too (no need to figure out how to invoke Emacs batch commands from scratch when you already have a working interface through eldev).&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;After using it in my daily workflow and letting it mature through real-world use, it's time to share what became a surprisingly robust little package. The final package is focused: a main &lt;code&gt;.el&lt;/code&gt; file plus a separate data file containing the processed kaomoji dataset. No complex dependencies beyond Emacs, no external processes, just pure Emacs Lisp doing what it does best: text manipulation and user interaction.&lt;/p&gt;

&lt;p&gt;Kaomel try following principles I value in Emacs packages: focused functionality, standard-compliant interfaces, and respect for user choice. Instead of imposing a particular workflow, it integrates with whatever completion framework users prefer.&lt;/p&gt;

&lt;p&gt;The current package works well for daily use and feels ready for a &lt;code&gt;1.0.0&lt;/code&gt; release, but I think there's room for more features. During early phases, I sketched out several ideas that could add value: easier selection for frequently used kaomojis, usage statistics, more sophisticated tagging. The clean separation between data processing and interface presentation means adding new features wouldn't require fundamental changes to the core architecture (at least, they shouldn't).&lt;/p&gt;

&lt;p&gt;What started as a simple utility became an occasion to explore different completion systems, Unicode strings handling, and (once again) the particular pleasures of lisp development. After using it daily for months, it's proven stable and useful enough to share. Also, I now have perfect kaomoji selection at my fingertips ｡◕‿◕｡&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Kaomel is available on GitHub and I've submitted the recipe to MELPA. If you're curious about the implementation details or want to contribute, the codebase is &lt;a href="https://github.com/gicrisf/kaomel" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>emacs</category>
      <category>opensource</category>
      <category>programming</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Building Testable Telegram Bots with Zustand</title>
      <dc:creator>Giovanni Crisalfi</dc:creator>
      <pubDate>Mon, 04 Aug 2025 00:08:51 +0000</pubDate>
      <link>https://forem.com/gicrisf/building-testable-telegram-bots-with-zustand-443g</link>
      <guid>https://forem.com/gicrisf/building-testable-telegram-bots-with-zustand-443g</guid>
      <description>&lt;p&gt;When most developers think of Zustand, they picture React hooks and component state. But what if I told you that Zustand's vanilla store could power a sophisticated Telegram bot with predictable state management and reactive behavior? &lt;/p&gt;

&lt;h2&gt;
  
  
  How It All Started
&lt;/h2&gt;

&lt;p&gt;This project began with two unrelated moments that collided perfectly. I was deep-diving into Zustand's internals, trying to understand how &lt;code&gt;createStore&lt;/code&gt; worked under the hood (the vanilla API that powers React hooks). Around the same time, my girlfriend complained about yet another obnoxious QR code website with ads, paywalls, and questionable privacy practices.&lt;/p&gt;

&lt;p&gt;"Challenge accepted!" I announced to my reflection in the black screen of my laptop. The challenge hadn't even been issued. I was literally having a conversation with myself, in my mind, and it totally changed the trajectory of my next few days. But that's how the best projects start, with a completely unprompted moment of caffeinated hubris at 2 AM.&lt;/p&gt;

&lt;p&gt;But instead of building another web app, I thought: &lt;em&gt;What if I made this a Telegram bot?&lt;/em&gt; It would be easier for her whole team to use than deploying a desktop app or a website and it would be easier for me to craft, since I wouldn't need to worry about UI niceties.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bot Complexity Gets Messy Fast
&lt;/h2&gt;

&lt;p&gt;Building a Telegram bot seems simple at first. Handle a message, generate a response, send it back. But as features grow, you quickly encounter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stateful conversations&lt;/strong&gt; (settings modes, multi-step flows)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Async operations&lt;/strong&gt; (file I/O, in this case: QR generation)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request tracking&lt;/strong&gt; (knowing what's processing, completed, or failed)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User preferences&lt;/strong&gt; (per-chat settings, format choices)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I considered existing libraries and frameworks, but I had a specific goal: &lt;strong&gt;I wanted something heavily testable from day one&lt;/strong&gt;. My girlfriend's team would depend on this for their workflow, so I couldn't risk shipping something buggy or unreliable. Moreover, I was pretty busy with work, so I had just a weekend to make it operational and leave it live on my server before Monday. I needed architectural patterns that would give me confidence in the code's correctness.&lt;/p&gt;

&lt;p&gt;The question became urgent during the initial development phase when I asked myself: &lt;em&gt;How do you unit-test a bot?&lt;/em&gt; Traditional bot code is a nightmare to test: it's all side effects, network calls, and stateful interactions mixed together. Of course, &lt;strong&gt;pure functions are infinitely easier to test&lt;/strong&gt; than side-effect heavy code.&lt;/p&gt;

&lt;p&gt;After some studying, I thought that the Elm unidirectional pattern would work perfectly not only for UIs, but for bots too. &lt;em&gt;(Don't worry if you're not familiar with Elm, I'll explain the pattern in the next section.)&lt;/em&gt; Writing a backend in Elm isn't that immediate and I wanted a dynamic language with a rich library system to go as fast as possible.&lt;/p&gt;

&lt;p&gt;I considered TypeScript; it would give me the type safety I wanted while keeping the flexibility of a dynamic ecosystem. But then it hit me: I had been using &lt;a href="https://zustand-demo.pmnd.rs/" rel="noopener noreferrer"&gt;Zustand&lt;/a&gt; to encapsulate business logic in React apps through pure state actions (I wrote about this approach in detail in my post on &lt;a href="https://dev.to/posts/20250301173228-building_robust_react_apps_with_zustand_and_immer/"&gt;Building Robust React Apps with Zustand and Immer&lt;/a&gt;). What if I could apply the same approach to a bot? Zustand's vanilla &lt;code&gt;createStore&lt;/code&gt; API could give me pure functions for business logic while keeping all the messy I/O operations separate and reactive.&lt;/p&gt;

&lt;p&gt;This looked like the perfect occasion to practice TypeScript and explore Zustand outside React. I was curious whether Zustand's specific advantages (vanilla store API, Immer integration, reactive subscriptions) could bring the same clarity to bot development that they bring to React apps.&lt;/p&gt;

&lt;p&gt;What started as a quick weekend project became an architectural experiment that showed me how Zustand could work beautifully in backend contexts.&lt;/p&gt;

&lt;p&gt;Let me walk you through what I experimented.&lt;/p&gt;

&lt;h2&gt;
  
  
  Zustand in the outer world
&lt;/h2&gt;

&lt;p&gt;Zustand's &lt;code&gt;createStore&lt;/code&gt; function works perfectly outside React:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createStore&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zustand/vanilla&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;produce&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;immer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createStore&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;State&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;Actions&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;// Initial state&lt;/span&gt;
  &lt;span class="na"&gt;chats&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;

  &lt;span class="c1"&gt;// Actions&lt;/span&gt;
  &lt;span class="na"&gt;newRequest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;format&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;produce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RequestState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;New&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;

  &lt;span class="na"&gt;processRequest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;produce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;RequestState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Processing&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No hooks, no components, just pure state management.&lt;/p&gt;

&lt;h2&gt;
  
  
  The code is TEA!
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frb58cqn25gz0xfehz3ol.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frb58cqn25gz0xfehz3ol.gif" alt="Anime scene pouring tea" width="500" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The bot follows a unidirectional data flow that any Elm developer would recognize:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Message → Update → Model → View (Bot Messages)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is The &lt;a href="https://guide.elm-lang.org/architecture/" rel="noopener noreferrer"&gt;Elm Architecture&lt;/a&gt; (TEA) adapted for Telegram: same principles, different runtime. If you're more familiar with &lt;strong&gt;&lt;a href="https://redux.js.org/" rel="noopener noreferrer"&gt;Redux&lt;/a&gt;&lt;/strong&gt;, it's the same pattern (Redux was inspired by Elm): actions trigger reducers that update state, which then triggers view updates (in this case, Telegram messages instead of DOM updates).&lt;/p&gt;

&lt;p&gt;Here's how a QR request flows through the system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 1. User sends message&lt;/span&gt;
&lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Create request (triggers state change)&lt;/span&gt;
  &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;newRequest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;format&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Start processing (triggers another state change)&lt;/span&gt;
  &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;processRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 4. Generate QR asynchronously&lt;/span&gt;
  &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;genQr&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;format&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;completeRequest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;abortRequest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Yeah, I like sugarfree promises.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Reactive State Subscriptions
&lt;/h2&gt;

&lt;p&gt;Instead of manually checking state or using callbacks, &lt;strong&gt;the bot reacts to state changes&lt;/strong&gt;, like those mechanical turrets in Portal that snap the moment they detect your movements through the glass.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft54o7y6ww0ax62rusybw.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft54o7y6ww0ax62rusybw.gif" alt="Portal turret detecting movement" width="314" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;"Are you still there?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;First, we set up a subscription that listens for any changes to the store:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentRequests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;currentRequests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentRequest&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;previousRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;previousRequests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;currentRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;previousRequest&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;currentRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;handleStateChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;previousRequest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;previousRequests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;currentRequests&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This subscription fires whenever the store state changes. We compare the current requests with the previous snapshot to detect state transitions (when a request moves from &lt;code&gt;New&lt;/code&gt; to &lt;code&gt;Processing&lt;/code&gt;, or from &lt;code&gt;Processing&lt;/code&gt; to &lt;code&gt;Completed&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;When a state change is detected, the bot automatically responds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleStateChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;RequestState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Processing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🔄 Generating your QR code...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;RequestState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Completed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendPhoto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createReadStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;RequestState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;❌ Something went wrong!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This is a simplified representation. The actual implementation also manages animated loading indicators, handles file cleanup, and includes additional error recovery logic (&lt;a href="https://github.com/gicrisf/qrbot/" rel="noopener noreferrer"&gt;check the code&lt;/a&gt; to see how).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  State Machine Pattern in Action
&lt;/h2&gt;

&lt;p&gt;Each request follows a clear lifecycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;RequestState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;New&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Processing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Completed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These states create a simple but effective state machine that prevents invalid transitions and makes the bot's behavior predictable. You can't accidentally send a "completed" message for a request that's still processing.&lt;/p&gt;

&lt;p&gt;Now that I think about it, there are more idiomatic ways to represent a &lt;code&gt;RequestState&lt;/code&gt; in TypeScript, like string literal unions (&lt;code&gt;'new' | 'processing' | 'completed' | 'error'&lt;/code&gt;), but I did like the idea of using a C-like enum when I wrote the bot, so... We'll leave it as it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Mode Conversations
&lt;/h2&gt;

&lt;p&gt;The bot supports different conversation modes (normal QR generation vs. settings configuration) through state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;ChatMode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Normal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// When user enters settings&lt;/span&gt;
&lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;settings/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;setChatMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ChatMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Bot commands change dynamically&lt;/span&gt;
  &lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setMyCommands&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/set_png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Set PNG format&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/set_svg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Set SVG format&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;chat_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The store tracks each chat's mode, and the reactive subscription updates bot behavior accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing: The Real Reason I Chose This Architecture
&lt;/h2&gt;

&lt;p&gt;Traditional bot code mixes core logic with I/O operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isProcessing&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;isProcessing&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Processing...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;qr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateQR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendPhoto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;qr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;isProcessing&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Testing this requires mocking the bot, filesystem, and network calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Traditional Bot Handler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;mockBot&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;mockFs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;mockBot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;mockResolvedValue&lt;/span&gt;&lt;span class="p"&gt;({}),&lt;/span&gt;
      &lt;span class="na"&gt;sendPhoto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;mockResolvedValue&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;mockFs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;mockResolvedValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;isProcessing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt; &lt;span class="c1"&gt;// Reset global state&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should handle message processing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// Test becomes complex due to async operations and mocks&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;messageHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mockBot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Processing...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// How do we test the QR generation without actual file I/O?&lt;/span&gt;
    &lt;span class="c1"&gt;// How do we verify the processing state without exposing internals?&lt;/span&gt;
    &lt;span class="c1"&gt;// How do we test error scenarios without triggering real network failures?&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The traditional approach forces you to mock everything and makes it difficult to isolate application logic from side effects.&lt;/p&gt;

&lt;p&gt;With Zustand's pure functions, testing becomes straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;QR Request Lifecycle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInitialState&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should handle request lifecycle correctly&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;newRequest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; 
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;QrFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Png&lt;/span&gt; 
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;RequestState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;New&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;processRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;RequestState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Processing&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;completeRequest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/path/to/qr.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;RequestState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Completed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/path/to/qr.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should prevent overload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Create 5 requests (hitting the limit)&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;newRequest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; 
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`test&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;QrFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Png&lt;/span&gt; 
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;BotState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Idle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 6th request should trigger overload&lt;/span&gt;
    &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;newRequest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; 
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;QrFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Png&lt;/span&gt; 
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;BotState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Overloaded&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why This Works
&lt;/h3&gt;

&lt;p&gt;My philosophy was simple: don't test Telegram APIs (that's their job), test my core functionality.&lt;/p&gt;

&lt;p&gt;Predictable Testing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No side effects&lt;/strong&gt; - Functions only change state, nothing else&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No mocking&lt;/strong&gt; - No external dependencies to stub&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast execution&lt;/strong&gt; - No network calls or file I/O in core operations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic&lt;/strong&gt; - Same input always produces same output&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Separation of Concerns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Application logic&lt;/strong&gt; lives in pure store actions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Side effects&lt;/strong&gt; (Telegram API calls, file generation) happen in reactions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy isolation&lt;/strong&gt; - Test core logic separately from integration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This testing approach caught several bugs during development:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Edge cases in overload handling&lt;/li&gt;
&lt;li&gt;Invalid state transitions that could crash the bot&lt;/li&gt;
&lt;li&gt;Race conditions in concurrent request processing&lt;/li&gt;
&lt;li&gt;Subtle bugs in request lifecycle management&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Built for the Future
&lt;/h2&gt;

&lt;p&gt;The beautiful part? This architecture seems scalable. Same store, different clients. Same logic, different interfaces. The store that powers the Telegram bot could power:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A CLI tool&lt;/li&gt;
&lt;li&gt;A React web interface for bulk QR generation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and other React-based UIs like Electron-ish desktop apps or React Native mobile apps.&lt;/p&gt;

&lt;p&gt;The core logic (request lifecycle, QR generation, state management) remains identical across all clients.&lt;/p&gt;

&lt;h2&gt;
  
  
  Library Choice
&lt;/h2&gt;

&lt;p&gt;You might be wondering about my library choice. I used the lower-level &lt;strong&gt;&lt;a href="https://github.com/yagop/node-telegram-bot-api" rel="noopener noreferrer"&gt;node-telegram-bot-api&lt;/a&gt;&lt;/strong&gt; rather than the higher-level &lt;strong&gt;&lt;a href="https://telegraf.js.org/" rel="noopener noreferrer"&gt;Telegraf&lt;/a&gt;&lt;/strong&gt;. This decision actually reinforces the architectural approach.&lt;/p&gt;

&lt;p&gt;I chose the lower-level &lt;strong&gt;node-telegram-bot-api&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;TelegramBot&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node-telegram-bot-api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TelegramBot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TELEGRAM_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;polling&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;start/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Welcome!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Raw message handling - you build the patterns&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compare that to Telegraf's opinionated patterns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Telegraf&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;telegraf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Telegraf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Welcome!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Processing...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;qr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateQR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replyWithPhoto&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;qr&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why node-telegram-bot-api?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unopinionated&lt;/li&gt;
&lt;li&gt;Direct mapping to Telegram Bot API&lt;/li&gt;
&lt;li&gt;Lightweight&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Composable primitives&lt;/strong&gt; (simple functions you can combine your own way)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why Telegraf?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Battle-tested patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rich middleware ecosystem&lt;/strong&gt; (sessions, rate limiting, analytics)&lt;/li&gt;
&lt;li&gt;Built-in error handling and recovery&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where Zustand/TEA Architecture Shines
&lt;/h3&gt;

&lt;p&gt;We've already covered the &lt;strong&gt;testing advantages&lt;/strong&gt; extensively. Beyond that, this architectural approach provides two key benefits that framework solutions typically can't match:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Multi-Client Architecture&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Same store powers bot AND future React web app&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Bot uses it&lt;/span&gt;
&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handleBotStateChanges&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// React app uses it too&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useQRStore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;useStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Observable State Machine&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The reactive architecture makes it trivial to add new capabilities. Want to build a dashboard to monitor all bot activity? Just add another subscription:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Every state transition is visible and reactive&lt;/span&gt;
&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;currentRequests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;RequestState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Completed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;sendQRToUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;logAnalytics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;updateDashboard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Trade-off
&lt;/h3&gt;

&lt;p&gt;Custom Architecture (with low-level APIs):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Control over every architectural decision&lt;/li&gt;
&lt;li&gt;Support for every client type (bot + web + CLI)&lt;/li&gt;
&lt;li&gt;Tracking for every state transition&lt;/li&gt;
&lt;li&gt;Testing for every business rule&lt;/li&gt;
&lt;li&gt;Maintainability for every future change&lt;/li&gt;
&lt;li&gt;Setup for every pattern you need&lt;/li&gt;
&lt;li&gt;Building for every abstraction you want&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Telegraf Framework:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rapid development&lt;/li&gt;
&lt;li&gt;Rich ecosystem (middleware, scenes)&lt;/li&gt;
&lt;li&gt;Battle-tested patterns&lt;/li&gt;
&lt;li&gt;Built-in error handling&lt;/li&gt;
&lt;li&gt;Framework lock-in&lt;/li&gt;
&lt;li&gt;Less architectural flexibility&lt;/li&gt;
&lt;li&gt;Harder to extend beyond bots&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You could theoretically use Telegraf for the Telegram plumbing while keeping your state architecture, but at that point, it would be more reasonable to just embrace the framework style. I think the hybrid approach would add complexity without clear benefits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;Functional programming patterns aren't just for pure functional languages and some frontend libraries aren't just for frontend projects. State management, reactive programming, and unidirectional data flow solve problems wherever complex state exists, including bots.&lt;/p&gt;

&lt;p&gt;The choice between framework convenience (Telegraf) and architectural control (custom Zustand) depends on your long-term goals. If you're building a system, not just a bot, the architectural investment provides a solid substrate for building complex systems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next time you're building something stateful outside the browser, consider bringing your frontend toolkit along.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Want to see the full implementation? Check out the &lt;a href="https://github.com/gicrisf/qrbot" rel="noopener noreferrer"&gt;source code&lt;/a&gt; and experiment with Zustand in your next non-React project.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>webdev</category>
      <category>tdd</category>
    </item>
    <item>
      <title>Zeeman: a minimalistic periodic table focused on isotopes</title>
      <dc:creator>Giovanni Crisalfi</dc:creator>
      <pubDate>Sun, 15 Jun 2025 12:59:01 +0000</pubDate>
      <link>https://forem.com/gicrisf/zeeman-a-minimalistic-periodic-table-focused-on-isotopes-mm1</link>
      <guid>https://forem.com/gicrisf/zeeman-a-minimalistic-periodic-table-focused-on-isotopes-mm1</guid>
      <description>&lt;p&gt;There’s a peculiar frustration familiar to anyone who’s worked with spectroscopic techniques like EPR or NMR: the hunt for isotopic data. You need both the spin and natural abundance of every isotope for an element, but these critical numbers are scattered across PDFs, paper tables, or, even worse, different websites. It takes &lt;em&gt;minutes&lt;/em&gt; to look them up! My programmer heart couldn’t bear such inefficiency.&lt;/p&gt;

&lt;h1&gt;
  
  
  Genesis
&lt;/h1&gt;

&lt;p&gt;This &lt;em&gt;profoundly&lt;/em&gt; inhuman struggle haunted me daily during my master’s thesis in Pharmaceutical Chemistry at the University of Bologna. Working with Prof. Lucarini’s group, we studied supramolecular compounds built from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An organic host with a &lt;em&gt;stable&lt;/em&gt; radical&lt;/li&gt;
&lt;li&gt;An inorganic metallic guest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The stable radical made these compounds perfect for EPR spectroscopy, but the metal guests often had multiple isotopes, each with its own spin. While rewriting a DOS Monte Carlo simulator in Rust (a side project called &lt;a href="https://github.com/gicrisf/esrafel" rel="noopener noreferrer"&gt;&lt;em&gt;Esrafel&lt;/em&gt;&lt;/a&gt;), I needed to simulate EPR spectra, which meant constantly cross-referencing isotope spins and abundances. My solution at the time? A Python CLI script that spat out isotope tables.&lt;/p&gt;

&lt;p&gt;For the script, I paired a pristine natural abundance ASCII table from NIST with something far rougher: a &lt;em&gt;hand-rolled&lt;/em&gt; dataset for spins I built from scratch. No tidy dataset existed, just inconsistent Wikipedia tables and textbook footnotes. I scraped, cleaned, and manually verified the values. Since I only cared about lab-relevant isotopes, I ignored the exotic ones only detectable in particle accelerators.&lt;/p&gt;

&lt;p&gt;It worked fine, but it was CLI-only (fine for me, useless for anyone else). Years later, as a professional software developer, I was looking for a project to test my own abilities in representing data with a beautiful library, &lt;code&gt;D3.js&lt;/code&gt;, especially using custom arcs and curves. Also, I thought that a table gets the job done, but a &lt;em&gt;visual&lt;/em&gt; representation could make spin distributions intuitive at a glance. I envisioned a double pie chart: inner rings for isotopic abundance, outer arcs clustering isotopes by spin. The idea &lt;em&gt;galvanized&lt;/em&gt; me, I had to build it.&lt;/p&gt;

&lt;p&gt;So I did. Today I’m happy to finally share &lt;em&gt;Zeeman&lt;/em&gt;: a free, interactive web app to make isotopic data as intuitive as a periodic table should be.&lt;/p&gt;

&lt;h1&gt;
  
  
  How It Works
&lt;/h1&gt;

&lt;p&gt;Like any periodic table, start selecting an element; the central panel will update instantly with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The element’s symbol&lt;/li&gt;
&lt;li&gt;Its atomic number&lt;/li&gt;
&lt;li&gt;A colorful thick inner ring&lt;/li&gt;
&lt;li&gt;A sleek black outer ring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The inner ring displays isotopic abundances, while the outer ring represents nuclear spin clusters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Navigation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Show Help&lt;/em&gt;: Explains the visualization logic.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Show Legend&lt;/em&gt;: Maps colors to nuclear spins.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Show Table&lt;/em&gt;, unlocks raw isotope data, displaying for each isotope:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mass number&lt;/li&gt;
&lt;li&gt;Relative atomic mass&lt;/li&gt;
&lt;li&gt;Spin&lt;/li&gt;
&lt;li&gt;Half-life&lt;/li&gt;
&lt;li&gt;Isotopic composition&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Finally, the &lt;em&gt;About&lt;/em&gt; button opens an about section.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Design Philosophy
&lt;/h3&gt;

&lt;p&gt;Brutalist foundations: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Immediate, information-dense visualizations. &lt;/li&gt;
&lt;li&gt;Flat, vibrant colors (no gradients)&lt;/li&gt;
&lt;li&gt;High-contrast combinations&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Under the Hood
&lt;/h1&gt;

&lt;p&gt;For the tech-curious, this is the stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;&lt;a href="https://react.dev/" rel="noopener noreferrer"&gt;React&lt;/a&gt;&lt;/em&gt; - UI library (doesn't need introductions)&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;&lt;a href="https://zustand.docs.pmnd.rs/" rel="noopener noreferrer"&gt;Zustand/Immer&lt;/a&gt;&lt;/em&gt; - state management with surgical updates and immutable safety&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;&lt;a href="https://d3js.org/" rel="noopener noreferrer"&gt;D3.js&lt;/a&gt;&lt;/em&gt; - powers my custom visualizations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://dev.to/posts/20250301173228-building-robust-react-apps-with-zustand-and-immer"&gt;I've already written about the beautiful Zustand/Immer combination&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;By the way, &lt;a href="https://github.com/gicrisf/zeeman" rel="noopener noreferrer"&gt;Zeeman is open-source on GitHub&lt;/a&gt;. Contributions and forks are all welcome. Leave a star if you like the project!&lt;/p&gt;

&lt;h3&gt;
  
  
  On D3.js
&lt;/h3&gt;

&lt;p&gt;Speaking of D3.js, it's not just another charting library, it's one of the most elegant libraries in the entire JavaScript ecosystem. New to it?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Explore live examples on &lt;a href="https://observablehq.com" rel="noopener noreferrer"&gt;Observable&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Read the creator's canonical post &lt;a href="https://bost.ocks.org/mike/join/" rel="noopener noreferrer"&gt;Thinking with Joins&lt;/a&gt; to understand its data-binding paradigm&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;About that last link:&lt;/em&gt; While it references an older D3 version, every single line of the post remains relevant. The newer APIs simply wrap the same core logic more elegantly. As I use D3 for other work too, we'll likely discuss it again here.&lt;/p&gt;

&lt;h1&gt;
  
  
  Who’s Zeeman For?
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Students&lt;/em&gt; exploring NMR/EPR isotope effects&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Researchers&lt;/em&gt; needing quick access to isotope data&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Educators&lt;/em&gt; creating interactive lessons&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Where Next?
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Mobile flow&lt;/em&gt;: Smoother touch interactions for lab tablets/phones&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Desktop port&lt;/em&gt;: A single executable for offline use&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Keyboard shortcuts&lt;/em&gt;: Faster navigation on desktop&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Export&lt;/em&gt;: High-res graph images for publications&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  The name
&lt;/h1&gt;

&lt;p&gt;The name pays homage to &lt;a href="https://www.nobelprize.org/prizes/physics/1902/zeeman/biographical/" rel="noopener noreferrer"&gt;Pieter Zeeman&lt;/a&gt;, whose work on magnetic fields and atomic spectra laid the foundation for modern NMR/EPR techniques.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusions
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;&lt;em&gt;Zeeman&lt;/em&gt;&lt;/em&gt; bridges the gap between raw isotopic data and human intuition (no more scavenging through PDFs or wrestling with tables). It’s the tool I wish I’d had during my thesis, now polished for everyone from students to researchers. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://zeeman-table.netlify.app/" rel="noopener noreferrer"&gt;Start exploring isotopes →&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>chemistry</category>
      <category>react</category>
      <category>d3</category>
    </item>
    <item>
      <title>Building Robust React Apps with Zustand and Immer</title>
      <dc:creator>Giovanni Crisalfi</dc:creator>
      <pubDate>Sun, 23 Mar 2025 22:30:20 +0000</pubDate>
      <link>https://forem.com/gicrisf/building-robust-react-apps-with-zustand-and-immer-482m</link>
      <guid>https://forem.com/gicrisf/building-robust-react-apps-with-zustand-and-immer-482m</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;For years, I dodged React like the plague. In fact, I avoided JavaScript altogether, even in web-related tasks. Take static site generators, for example. For my old chemistry blog, I experimented with a variety of tools, year by year: Pelican, Jekyll, Hugo, Grav... In the end, I settled on Zola. Fast, robust, no JS needed, perfect for CI/CD workflows. Just prerendered HTML and sprinkle scripts for flair (e.g. comments, a masonry in the home page ecc.).&lt;/p&gt;

&lt;p&gt;Rust’s elegance and stability, though less prominent in web development, shine through in Zola, more than compensating for any niche limitations.&lt;br&gt;
This, however, is a particular case, since we're just presenting static content: basically, most of the work is collapsed on the backend. But actual &lt;strong&gt;web apps&lt;/strong&gt;? Rust’s Yew or Dioxus are promising but young.&lt;/p&gt;

&lt;p&gt;I love to find refuge in Elm's elegance and solidity, but recently, I’ve been drawn back to React and JavaScript, both professionally and in personal projects. Time constraints and the appeal of leveraging the extensive ecosystem of UI libraries have played a significant role in this shift. What I discovered was a React that had matured significantly, driven by its hook system and TypeScript support.&lt;/p&gt;

&lt;p&gt;Particularly, I stumbled upon a couple of libraries that have the potential to transform even the most complex app development workflows: Immer and Zustand. In this post, I’ll show you how these two libraries interoperate seamlessly, enabling you to architect applications that transcend the entropic morass of lifecycle chaos that once defined React’s class components.&lt;/p&gt;
&lt;h2&gt;
  
  
  Zustand: a minimalist state solution
&lt;/h2&gt;

&lt;p&gt;As a longtime admirer of Redux (and its Elm/Flux-inspired architecture), I initially leaned on its strict unidirectional flow. But Redux’s boilerplate often felt at odds with React’s evolving simplicity.&lt;/p&gt;

&lt;p&gt;Let’s begin with the simplest case possible, something you can handle using a basic &lt;code&gt;useState&lt;/code&gt; in React: a counter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Increment&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example initializes a state variable &lt;code&gt;count&lt;/code&gt; with an initial value of &lt;code&gt;0&lt;/code&gt; and provides a button to increment it. How can you achieve the same with Zustand? First of all, you install Zustand.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;zustand
&lt;span class="c"&gt;# or `bun add zustand`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The following code replicates the same behavior as the previous one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;create&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zustand&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;})),&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;increment&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useStore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Increment&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;useStore&lt;/code&gt; is created using &lt;code&gt;zustand&lt;/code&gt;'s &lt;code&gt;create&lt;/code&gt; function.&lt;/li&gt;
&lt;li&gt;  It initializes a state with &lt;code&gt;count: 0&lt;/code&gt; and an &lt;code&gt;increment&lt;/code&gt; function that updates &lt;code&gt;count&lt;/code&gt; by 1.&lt;/li&gt;
&lt;li&gt;  In the &lt;code&gt;App&lt;/code&gt; component, &lt;code&gt;useStore&lt;/code&gt; is used to access &lt;code&gt;count&lt;/code&gt; and &lt;code&gt;increment&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  The current &lt;code&gt;count&lt;/code&gt; is displayed, and a button triggers &lt;code&gt;increment&lt;/code&gt; when clicked.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This case resembles the previous one but has nuanced differences that become important in structured contexts. Unlike &lt;code&gt;useState&lt;/code&gt;, which provides a dedicated value-setter pair, Zustand actions should encapsulate entire workflows rather than individual parameter updates (hence we call the action &lt;code&gt;increment&lt;/code&gt;, not just &lt;code&gt;setCount&lt;/code&gt;). More complex scenarios will illustrate this distinction clearly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Effortless immutability with Immer
&lt;/h2&gt;

&lt;p&gt;Zustand simplifies state management, yet its requirement for immutable updates in JavaScript can feel like a labyrinthine chore, especially when dealing with nested state — which is basically all the time. Immer operates like a tiny JavaScript alchemist, transmuting mutable-like code into immutable state updates.&lt;/p&gt;

&lt;p&gt;Let’s revisit the counter example, but this time, imagine we need to manage a more complex state object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;history&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;increment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;prevState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prevState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;history&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;prevState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prevState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Increment&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we’re updating both &lt;code&gt;count&lt;/code&gt; and &lt;code&gt;history&lt;/code&gt;, which requires manually spreading and nesting state. This quickly becomes error-prone as state grows more complex.&lt;/p&gt;

&lt;p&gt;With Immer, the same logic becomes cleaner and more intuitive. First, install Immer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;immer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let’s rewrite the increment function using Immer. Import &lt;code&gt;produce&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;produce&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;immer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;produce&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;increment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;produce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nx"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;produce&lt;/code&gt; takes a "draft" of the state, allowing you to write mutable-style code.&lt;/li&gt;
&lt;li&gt;  The &lt;code&gt;draft&lt;/code&gt; is basically a proxy, and Immer automatically generates the next immutable state for you.&lt;/li&gt;
&lt;li&gt;  This eliminates the need for manual spreading and nesting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Immer shines in scenarios where state is deeply nested or requires frequent updates. Its symbiosis with Zustand will become noticeable when we explore the middleware provided by Zustand itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  I Saw the Canvas Glow: a practical example
&lt;/h2&gt;

&lt;p&gt;Let’s design a slightly more complex scenario.&lt;/p&gt;

&lt;h3&gt;
  
  
  The challenge
&lt;/h3&gt;

&lt;p&gt;We want a web app where users can interact with a canvas. When a user clicks on the canvas, a point is added.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// These are private variables declared somewhere&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="c1"&gt;// Points are added exclusively through this exported function&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addPoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Add the point to the list of points&lt;/span&gt;
    &lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// A function to reset the state&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;very_rude_reset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;points&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// You maybe want a function to reset the state&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;very_rude_reset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;points&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="c1"&gt;// You may want additional functionality, but let's focus on the main part&lt;/span&gt;
&lt;span class="c1"&gt;// * adding points *&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What’s the problem? Before storing the point, we need to perform a check on a remote server. This could take some time. However, we shouldn’t let this hinder us from developing a reactive application. The GUI must remain responsive and should provide feedback to the user, indicating that the app is working without appearing sluggish.&lt;/p&gt;

&lt;p&gt;So, when a point is added, the app enters a &lt;strong&gt;loading state&lt;/strong&gt;, waiting for the server’s response.&lt;/p&gt;

&lt;p&gt;You can see the app in action &lt;a href="https://chimerical-semifreddo-fe6dbc.netlify.app/" rel="noopener noreferrer"&gt;right here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;How does all of this work?&lt;/p&gt;

&lt;p&gt;To not overcomplicate the example, we simulate the server call with a timeout of 10 seconds, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addPoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// We're now loading again&lt;/span&gt;
        &lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Instead of this fetch...&lt;/span&gt;
        &lt;span class="c1"&gt;// fetch('/api/update', {&lt;/span&gt;
        &lt;span class="c1"&gt;//     method: 'POST',&lt;/span&gt;
        &lt;span class="c1"&gt;//     headers: { 'Content-Type': 'application/json' },&lt;/span&gt;
        &lt;span class="c1"&gt;//     body: JSON.stringify(newPoint),&lt;/span&gt;
        &lt;span class="c1"&gt;// })&lt;/span&gt;
        &lt;span class="c1"&gt;// ...a handcrafted promise:&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Emulating a 10 seconds job&lt;/span&gt;
            &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Adding point to the list of points...&lt;/span&gt;
            &lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Job completed after 10 sec. Ready again.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// Other desirable actions...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See? Very simple. This way, we can test how out application behave in the worst case scenario.&lt;/p&gt;

&lt;p&gt;Some (OOO people) might argue that this could be represented with a class and solved using encapsulation in an object, but we intentionally avoided that approach because we’re taking the functional path. Let’s look at the real implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  The component
&lt;/h3&gt;

&lt;p&gt;We're working in React (you didn’t forget, did you?). Let’s take a look at the main &lt;code&gt;App&lt;/code&gt; component, which handles canvas rendering and user interactions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Everyday stuff&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useStore&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./Store&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- Zustand store!&lt;/span&gt;
&lt;span class="c1"&gt;// (We'll explore it right below)&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvasRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLCanvasElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;addPoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useStore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Effect for animations and aesthetic stuff&lt;/span&gt;
  &lt;span class="c1"&gt;// useEffect(() =&amp;gt; { ... })&lt;/span&gt;

  &lt;span class="c1"&gt;// Handle left-click on the canvas to add a node&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvasRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleMouseClick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MouseEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;// Left click&lt;/span&gt;
            &lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
              &lt;span class="na"&gt;X&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;Y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mousedown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleMouseClick&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mousedown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleMouseClick&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="c1"&gt;// JSX with a title, message, canvas, and list of points&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[...]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might have noticed the logic is missing here: that's because it is now fully encapsulated within Zustand.&lt;/p&gt;

&lt;h3&gt;
  
  
  The store
&lt;/h3&gt;

&lt;p&gt;We’ve already seen how the &lt;code&gt;create&lt;/code&gt; function works: you define both state variables and actions (functions that modify the state) inside the &lt;code&gt;create&lt;/code&gt; function. Next, let’s examine the Immer integration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;create&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zustand&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;immer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zustand/middleware/immer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;immer&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- Immer middleware&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Click on the mysterious globe.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;setMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;// Adding points: we’re almost there, wait just a little more!&lt;/span&gt;
  &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essentially, we're wrapping the state creation inside another function, giving us Immer’s capabilities without explicitly calling &lt;code&gt;produce&lt;/code&gt;. In this example, we’re focusing on the &lt;code&gt;message=/=setMessage&lt;/code&gt; pair, as it’s the simplest way (I could think of) to demonstrate state mutation using the Immer middleware in Zustand.&lt;/p&gt;

&lt;p&gt;Let’s encapsulate the logic we’ve seen earlier using this approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;create&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zustand&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;immer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zustand/middleware/immer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;immer&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- Immer middleware&lt;/span&gt;
        &lt;span class="c1"&gt;// [message/setMessage as before]&lt;/span&gt;
        &lt;span class="c1"&gt;// State variable for points&lt;/span&gt;
        &lt;span class="na"&gt;points&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
        &lt;span class="c1"&gt;// Adding points&lt;/span&gt;
        &lt;span class="na"&gt;addPoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;point&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;setMessage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="c1"&gt;// `isLoading` is now stored inside the store,&lt;/span&gt;
            &lt;span class="c1"&gt;// so we use the `get()` function&lt;/span&gt;
            &lt;span class="c1"&gt;// provided by the Immer middleware (up here)&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;});&lt;/span&gt;
                &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Point pushed. Now loading...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="c1"&gt;// Emulate a 10-second job&lt;/span&gt;
                    &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Job completed after 10 seconds. You can click again.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
                &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Not now, my friend. I'm busy.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ve done it! The &lt;code&gt;addPoint&lt;/code&gt; action demonstrates handling asynchronous operations, such as the 10-second loading period (or, more realistically, a server call). Zustand doesn’t care whether your functions are synchronous or asynchronous (as long as they make sense).&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;In the end, Zustand and Immer make a strong pair for state management in React. Missing Rust/Elm’s guarantees? You’ll never fully replicate them in JS, but TypeScript, paired with modern tooling, partially bridges the gap. Zustand cuts through the noise with its clean, focused approach. Immer smooths out the rough edges of immutable updates, especially in nested states. Together, they let you build solid, fast apps with less code and fewer mistakes.&lt;/p&gt;

&lt;p&gt;The discussed example demonstrates how these tools can handle both synchronous and asynchronous operations seamlessly. To explore the implementation further, check out the repository &lt;a href="https://github.com/gicrisf/canvas-glow" rel="noopener noreferrer"&gt;here&lt;/a&gt;. If you find it useful and enjoyed this blog post, don't forget to leave a star!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>typescript</category>
      <category>react</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Getting Started with Hy, the Python Lisp: a Matplotlib example - Ep. 2</title>
      <dc:creator>Giovanni Crisalfi</dc:creator>
      <pubDate>Thu, 10 Aug 2023 15:17:04 +0000</pubDate>
      <link>https://forem.com/gicrisf/getting-started-with-hy-the-python-lisp-a-matplotlib-example-ep-2-7jm</link>
      <guid>https://forem.com/gicrisf/getting-started-with-hy-the-python-lisp-a-matplotlib-example-ep-2-7jm</guid>
      <description>&lt;p&gt;In the &lt;a href="https://dev.to/gicrisf/getting-started-with-hy-the-python-lisp-a-simple-matplotlib-example-ep-1-2deb"&gt;previous episode&lt;/a&gt;, we briefly saw what Hy is, what it means that it is a dialect of Lisp, how to install it, and step by step, we wrote a small example to create a line plot in matplotlib, strictly following the imperative approach we would have used in Python.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hylang"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;matplotlib.pyplot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;setv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;setv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;setv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;plt.subplots&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.plot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:marker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"o"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.set-xlabel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"X values"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.set-ylabel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Y values"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.set-title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Simple Line Plot"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;plt.savefig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"img/hy-plt-example.png"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you don't understand something about the code above, I recommend referring to the first part: maybe something just went unnoticed until now.&lt;br&gt;
If, instead, everything is clear, we can proceed. Now, it's time to make things more spicy by adopting a functional approach.&lt;/p&gt;

&lt;p&gt;More precisely, we will begin by encapsulating everything in a &lt;code&gt;let&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is a let?
&lt;/h2&gt;

&lt;p&gt;Before rewriting our code, it is important to understand what a "let" is and why it is so useful.&lt;br&gt;
If you are already familiar with this concept, feel free to skip this paragraph and proceed directly to the code.&lt;/p&gt;

&lt;p&gt;A "let" is a Lisp operator that allows you to create a lexical context with new local variables. Basically, it's like saying you want to call a function that can access these variables and prioritize these variable declarations over other external ones with the same name.&lt;/p&gt;

&lt;p&gt;How does it works?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A let expression has two parts. First comes a list of instructions for creating variables, each of the form (variable expression). Each variable will initially be set to the value of the corresponding expression. [...]&lt;br&gt;
After the list of variables and values comes a body of expressions, which are evaluated in order.&lt;br&gt;
(Graham, ANSI Common Lisp, 1996, p. 20)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In Hy terms,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hylang"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;x&lt;/code&gt; variable defined in the head of the let is not accessible outside it.&lt;/p&gt;

&lt;p&gt;Actually, the Hy let is pretty special, because it makes you write variables that have access to the ones defined previously in the same expression.&lt;br&gt;
For example,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hylang"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see here, the &lt;code&gt;z&lt;/code&gt; variable is defined in relation to &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt;.&lt;br&gt;
&lt;a href="https://docs.hylang.org/en/stable/api.html?highlight=let*#let" rel="noopener noreferrer"&gt;As the documentation explains&lt;/a&gt;,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Like the let* of many other Lisps, let executes the variable assignments one-by-one, in the order written&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This means that, as &lt;code&gt;let*&lt;/code&gt; in other lisps, &lt;code&gt;let&lt;/code&gt; in Hy is functionally equivalent to a series of pure nested lets.&lt;/p&gt;
&lt;h2&gt;
  
  
  Let('s) plot
&lt;/h2&gt;

&lt;p&gt;Being conscious of how &lt;code&gt;let&lt;/code&gt; works in Hy, we can now rewrite the initial code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hylang"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;matplotlib.pyplot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;y-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;plt.subplots&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.plot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:marker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"o"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.set-xlabel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"X values"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.set-ylabel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Y values"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.set-title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Simple Line Plot"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;plt.savefig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"img/hy-plt-example.png"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember that let blocks can be nested. For example, in the case we want to plot two identical graphs but with different labels, we could write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hylang"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;matplotlib.pyplot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;y-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="c1"&gt;; English version&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;plt.subplots&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.plot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:marker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"x"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.set-xlabel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"X values"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.set-ylabel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Y values"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.set-title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"First Title"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;plt.savefig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"img/hy-plt-example-en.png"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="c1"&gt;; Italian version&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;plt.subplots&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.plot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:marker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"o"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.set-xlabel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Ascisse"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.set-ylabel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Ordinate"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.set-title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Secondo titolo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;plt.savefig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"img/hy-plt-example-it.png"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code saves two figures: an English one, and an Italian one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Macros
&lt;/h2&gt;

&lt;p&gt;Lisp code, including Hy code, is expressed as a series of lists. Its minimalist syntax (or rather, the lack of it) allows for easy writing of macros.&lt;br&gt;
But what is a macro?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The most common way to write programs that write programs is by defining macros. Macros are operators that are implemented by transformation. You define a macro by saying how a call to it should be translated. This translation, called macro-expansion, is done automatically by the compiler. So the code generated by your macros becomes an integral part of your program, just as if you had typed it in yourself. (Graham, ANSI Common Lisp, 1996, p. 162)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In other words, macros are operators that write code and lisps are particularly good for metaprogramming, which means the code itself is treated as data that can be manipulated by itself, allowing for powerful generative techniques. This means also that we can build up our own language to make the code drier or more comfortable.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Lisp is designed to be extensible: it lets you define new operators yourself. (Graham, ANSI Common Lisp, 1996, p. 3)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Going meta
&lt;/h2&gt;

&lt;p&gt;Let's see how we can leverage the power of macros in this case.&lt;br&gt;
If you require a series of similar plots with only the title and filename being changed, you can write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hylang"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;matplotlib.pyplot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defmacro&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plot-with-title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;quasiquote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;plt.subplots&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.plot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:marker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"o"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.set-xlabel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Ascisse"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.set-ylabel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Ordinate"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.set-title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;unquote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;plt.savefig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;unquote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;)))))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;y-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;plot-with-title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"First title"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"img/hy-plt-example-one.png"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;plot-with-title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Second title"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"img/hy-plt-example-two.png"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can dream bigger by creating abstractions for all line plots.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hylang"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;matplotlib.pyplot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defmacro&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lineplot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;xlabel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ylabel&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;quasiquote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;unquote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="n"&gt;y-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;unquote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;plt.subplots&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;

                &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.plot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:marker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;unquote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.set-xlabel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;unquote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;xlabel&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.set-ylabel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;unquote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ylabel&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.set-title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;unquote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;plt.savefig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;unquote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;)))))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;lineplot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s"&gt;"Titolo"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s"&gt;"img/hy-plt-example-one.png"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s"&gt;"o"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s"&gt;"X values"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s"&gt;"Y values"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As long as the macro definition is under your eyes, it's easy to understand the order in which to write the arguments. In this example: first the values for x and y, then the title, followed by the path of the image to be saved, the marker, and the labels. But this is just a simplified situation: usually, the macro definition is hidden in a library out of sight, so named arguments should be preferred over positional arguments.&lt;/p&gt;

&lt;p&gt;Unfortunately, this is not directly possible in Hy because, &lt;a href="https://docs.hylang.org/en/master/api.html#defmacro" rel="noopener noreferrer"&gt;as described in the documentation&lt;/a&gt;,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;defmacro&lt;/code&gt; cannot use keyword arguments, because all values are passed to macros unevaluated. All arguments are passed positionally, but they can have default values:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hylang"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defmacro&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a-macro&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;;; (a-macro 2)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; [2 1]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; (a-macro 2 3)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; [2 3]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; (a-macro :b 3)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; [:b 3]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thankfully, there's a nifty workaround to achieve to have both the default values and the positional arguments.&lt;br&gt;
Instead of them being assigned in the macro, we write a simple wrapper function. Then we use the function for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Defining the default values;&lt;/li&gt;
&lt;li&gt;  Giving names to macro's positional arguments.&lt;/li&gt;
&lt;/ul&gt;




&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hylang"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;matplotlib.pyplot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defmacro&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;macro-lineplot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="n"&gt;xlabel&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="n"&gt;ylabel&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;quasiquote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;unquote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="n"&gt;y-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;unquote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;plt.subplots&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;

                &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.plot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:marker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;unquote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.set-xlabel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;unquote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;xlabel&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.set-ylabel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;unquote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ylabel&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.set-title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;unquote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;plt.savefig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;unquote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;)))))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lineplot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;*&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Title"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"fname.png"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"o"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;xlabel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"X values"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ylabel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Y values"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;macro-lineplot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;xlabel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ylabel&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://docs.hylang.org/en/stable/api.html?highlight=named%20argument#defn" rel="noopener noreferrer"&gt;The asterisk&lt;/a&gt; (*) tells which parameters need the key:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If the symbol * is given in place of a parameter, it means that all the following parameters can only be set by name.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In other words, we have assigned default values that can be overridden by using the key's name.&lt;/p&gt;

&lt;p&gt;Ultimately, assuming the macro is externalized in a library, the original code simplifies to these practical and fully functional lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hylang"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;lineplot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="no"&gt;:title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Simple Line Plot"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="no"&gt;:fname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"img/hy-plt-example-last.png"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course, this is the simplest example I could think of, and there is room for a lot of improvement, but I think it makes the potential of the language and macros quite evident.&lt;br&gt;
Moreover, keep in mind that the thrill lies in the fusion of these two powerful languages. By keeping the &lt;code&gt;hy&lt;/code&gt; package as a dependency, Hy macros can be imported and used directly in Python code. This allows you to easily share the wrapper function with non-Hy users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this post, we have discussed how to effectively use lexical scoped blocks and macros. Macros provide a powerful tool for metaprogramming, allowing you to extend the language and write code that is both cleaner and more expressive. By understanding and utilizing these features in Hy, you will be able to write more reliable code while still benefiting from Python libraries.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally posted on &lt;a href="https://digressions.zwit.link/en/software/getting-started-with-hy-python-lisp-matplotlib-ep-2/" rel="noopener noreferrer"&gt;Zwitterionic Digressions&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>lisp</category>
      <category>hylang</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Getting Started with Hy, the Python Lisp: a simple Matplotlib example - Ep. 1</title>
      <dc:creator>Giovanni Crisalfi</dc:creator>
      <pubDate>Mon, 07 Aug 2023 13:41:02 +0000</pubDate>
      <link>https://forem.com/gicrisf/getting-started-with-hy-the-python-lisp-a-simple-matplotlib-example-ep-1-2deb</link>
      <guid>https://forem.com/gicrisf/getting-started-with-hy-the-python-lisp-a-simple-matplotlib-example-ep-1-2deb</guid>
      <description>&lt;p&gt;Hy is a Lisp dialect that combines the expressive power of Lisp with the convenience and extensive libraries of Python.&lt;/p&gt;

&lt;p&gt;It's cool because it doesn't transpile in Python, but converts the code directly into its abstract syntax tree (AST), so interoperability with libraries is guaranteed, and you don't have to deal with an intermediate layer of Python code.&lt;/p&gt;

&lt;p&gt;I won't deliver a long theoretical speech about the beauty of Lisp on this occasion. If you're already familiar with Lisp dialects like Emacs Lisp, Common Lisp, Clojure, or Scheme, this is the place for you. If you've never heard of Lisp, this is a good place to start exploring its fundamentals by working with code firsthand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;First of all, we need to install Hy. Installing Hy is as simple as installing any library in Python.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;git+https://github.com/hylang/hy.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The package provides a CLI tool for running Hy code by simply passing it the file name. Additionally, the package includes a terminal REPL that can be launched by entering &lt;code&gt;hy&lt;/code&gt; in the shell, along with some other useful features. For comprehensive details, check &lt;code&gt;hy --help&lt;/code&gt;. It is also possible to install Hy in a virtual environment if you prefer not to directly touch the system. Assuming the reader is familiar with Python, I won't explain the process of creating and managing a virtual environment. Instead, we can proceed directly to the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Importing a library
&lt;/h2&gt;

&lt;p&gt;Imagine wanting to plot a simple series of points with Python. There are many libraries to achieve this, but Matplotlib is probably the most famous and widely used. So, the first thing we would write in Python is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hylang"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;matplotlib.pyplot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Doing so, we would import the module &lt;code&gt;pyplot&lt;/code&gt; from the library &lt;code&gt;matplotlib&lt;/code&gt; and temporarily name it &lt;code&gt;plt&lt;/code&gt; for brevity. In Hy, achieving the same result is straightforward: as in every other Lisp, each expression is enclosed in parentheses, with the first element acting as a function and the following elements as arguments. This simplicity makes the result quite intuitive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hylang"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;matplotlib.pyplot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setting variables
&lt;/h2&gt;

&lt;p&gt;Here, we set the point coordinates. We do this in two lists: one for the x-axis and the other for the y-axis. In Python, we would have written:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;x_values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;y_values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Hy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hylang"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;setv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;setv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, the variables must be set with the &lt;code&gt;setv&lt;/code&gt; (set variable) function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unpacking values
&lt;/h2&gt;

&lt;p&gt;Now we aim to assign fig and ax simultaneously, conventionally denoted as &lt;code&gt;fig&lt;/code&gt; and &lt;code&gt;ax&lt;/code&gt;, which are returned by a method of &lt;code&gt;plt&lt;/code&gt; called &lt;code&gt;subplots()&lt;/code&gt;. In Python words:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create a figure and axis object
&lt;/span&gt;&lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subplots&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In other words, we have to master two operations: unpacking data from a function's return and executing a method.&lt;/p&gt;

&lt;p&gt;Unpacking data is really simple, assign one list to another, ensuring equal values in both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hylang"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;setv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; (print a) =&amp;gt; 1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; (print b) =&amp;gt; 2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works with tuples too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hylang"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;setv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tuple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Calling methods
&lt;/h2&gt;

&lt;p&gt;Calling an object method is, also in this case, quite simple. You use the same (short) notation that distinguishes the call in Python: &lt;code&gt;plt.subplots()&lt;/code&gt;. However, in this case, instead of placing parentheses after the term to clarify that we are invoking a function, we place them surrounding the function itself, like this: &lt;code&gt;(plt.subplots)&lt;/code&gt;. This is the lispy way. Putting things together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hylang"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;setv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;plt.subplots&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that the &lt;code&gt;ax&lt;/code&gt; object has been created, we can call one of its methods to plot our values. This time, the method has arguments. In Python, we would write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Plot the data as a line plot
&lt;/span&gt;&lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x_values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remembering that in every Lisp, the initial term within the grouping of parentheses assumes the role of the function, succeeded thereafter by the arguments, the same principale extends to methods, wich are -- indeed -- function themselves:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hylang"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.plot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y-values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, we may also want to specify a specific argument by name. We can do this by using a notation that should be familiar to us as it has already been seen in the &lt;code&gt;import&lt;/code&gt; above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hylang"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.plot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:marker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"o"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are about to reach the conclusion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting all together
&lt;/h2&gt;

&lt;p&gt;All that remains is to recall other methods of &lt;code&gt;ax&lt;/code&gt; to define the labels and the title.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hylang"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.set-xlabel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"X values"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.set-ylabel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Y values"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.set-title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Simple Line Plot"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, let's gather all the code we have written and cap it with a &lt;code&gt;savefig&lt;/code&gt; to admire our effort's fruit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hylang"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;matplotlib.pyplot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;setv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;setv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;setv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;plt.subplots&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.plot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:marker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"o"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.set-xlabel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"X values"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.set-ylabel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Y values"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ax.set-title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Simple Line Plot"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;plt.savefig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"img/hy-plt-example.png"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2xipepvqzw0sia8jph7q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2xipepvqzw0sia8jph7q.png" alt="The rendered plot" width="800" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;This first part was meant to be a gentle introduction for anyone, so I made sure to faithfully follow the style that would be adopted in Python. While switching to Hy may not immediately showcase its advantages, with patience, its benefits will become clear. In the next episode, we will code using a functional approach and then we will abstract the logic into a small macro. This will spare us from writing all the tedious boilerplate.&lt;/p&gt;

&lt;p&gt;The second part is available both here on dev.to and on my blog.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stay connected!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally posted on &lt;a href="https://digressions.zwit.link/en/software/getting-started-with-hy-python-lisp-matplotlib-ep-1/" rel="noopener noreferrer"&gt;Zwitterionic Digressions&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>beginners</category>
      <category>hylang</category>
      <category>lisp</category>
    </item>
    <item>
      <title>Scribbling pixels with Aseprite</title>
      <dc:creator>Giovanni Crisalfi</dc:creator>
      <pubDate>Mon, 22 May 2023 23:52:18 +0000</pubDate>
      <link>https://forem.com/gicrisf/scribbling-pixels-with-aseprite-153a</link>
      <guid>https://forem.com/gicrisf/scribbling-pixels-with-aseprite-153a</guid>
      <description>&lt;p&gt;&lt;a href="https://www.zwitterio.it/en/2023/scribbling-pixels-with-aseprite/" rel="noopener noreferrer"&gt;&lt;em&gt;Originally posted on zwitterio.it&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have been fascinated by pixel art since I can remember.&lt;br&gt;
For years, I've been having fun experimenting with pixel art programs, and I fell in love with dithering when I started messing with my blog generator code.&lt;br&gt;
More generally, I have always loved visual art, but lacking what I thought were the necessary artistic skills for traditional painting methods, I never thought I could personally make it.&lt;br&gt;
Yet, exposure to the works of talented people on the Fediverse made me wonder about scribbling in pixels.&lt;/p&gt;

&lt;p&gt;What if I tried crafting pixels by hand too?&lt;br&gt;
What could go wrong? (&lt;em&gt;Everything can go wrong, I have no time for doing this!&lt;/em&gt;, says a side of my mind I choose to blatantly ignore).&lt;/p&gt;

&lt;p&gt;I really love &lt;a href="https://www.gimp.org/" rel="noopener noreferrer"&gt;GIMP&lt;/a&gt;, but my past attempts with GIMP have never led me very far.&lt;br&gt;
Maybe it was because it is more general-purpose, so, never having done pixel art &lt;em&gt;by hand&lt;/em&gt;, I wouldn't know how to adapt the workspace to the needs of my pixels.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://lia.fail/objects/fc45583d-2cbf-4db7-8e32-7c26a8305ab4" rel="noopener noreferrer"&gt;Then Nico posted a screenshot of her Desktop.&lt;/a&gt;&lt;br&gt;
While I was admiring her icon pack, I noticed this name: &lt;a href="https://www.aseprite.org/" rel="noopener noreferrer"&gt;Aseprite&lt;/a&gt;.&lt;br&gt;
When I looked up for it, I was blown away by the UI of this software: so vintage, so cool, I had to try it.&lt;br&gt;
(I know, I know, I'm the type who gets hooked by a book cover.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fit9ceewa8pc7jn8fvz1c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fit9ceewa8pc7jn8fvz1c.png" alt="Figure 1: Screenshot of Aseprite while showing my sprite of a dioxane molecule." width="800" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here I want to leave my very first attempt at using Aseprite: a simple 16x16 binary image representing a molecule of 1,4-dioxane (basically the first thing I could think of).&lt;/p&gt;

&lt;p&gt;Those are just some pixels scattered in a white field, but for my eyes, they hold a poetic charm.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjl9mi7c063n03ib00qgj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjl9mi7c063n03ib00qgj.png" alt="Figure 2: A pixel art version of my Mastodon profile picture." width="640" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Immediately after, I embarked on something more exciting: the faithful reproduction of my Mastodon profile picture into its corresponding pixel art; it represents a 4-eyed me through the lens of a &lt;a href="https://lospec.com/palette-list/cga-palette-0-high" rel="noopener noreferrer"&gt;CGA0 4-color palette&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Things escalated quickly and I tried pixelating everything: old logos, random images, pictures of hallways and so on.&lt;/p&gt;

&lt;p&gt;For this burst of passion, I am grateful to the pixel artists of the Fediverse, and particularly Nico, who accidentally introduced me to this lovely software. Because of them, I now aspire to craft many more works.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>pixelart</category>
      <category>image</category>
      <category>community</category>
    </item>
    <item>
      <title>Start Qute Now</title>
      <dc:creator>Giovanni Crisalfi</dc:creator>
      <pubDate>Tue, 13 Dec 2022 23:06:05 +0000</pubDate>
      <link>https://forem.com/gicrisf/start-qute-now-336o</link>
      <guid>https://forem.com/gicrisf/start-qute-now-336o</guid>
      <description>&lt;p&gt;A few days ago, I started using Qutebrowser. I mean using it for real. It wasn't my first installation: I tried it other times in the past, but I had never considered leaving Firefox. This time I'm using it as a daily driver, migrating my entire workflow into it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Originally posted on &lt;a href="https://www.zwitterio.it/en/software/start-qute-now/" rel="noopener noreferrer"&gt;zwitterio.it&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But why I had this mad idea of leaving Firefox in the first place? Because it was the only software left that made me use the mouse systematically, over and over. Yeah, of course, you can set a lot of keybindings in Firefox, and you can install addons like Vim Vixen for getting to use it with the keyboard only, but you still feel the friction because the support isn't native, therefore every command has to be channeled through a lot of javascript subway sewers. It's not Firefox's fault, though: &lt;strong&gt;Firefox isn't imagined for a keyboard-only approach&lt;/strong&gt; because it's not developed with that kind of user in mind. There is nothing wrong with it. Maybe, if old extensions weren't been abandoned, we could have had a way to shape it as &lt;strong&gt;a real keyboard-driven browser&lt;/strong&gt;, but the total turning on WebExtension wasn't a choice really made by Mozilla, so let's avoid digressing. The point is: Firefox isn't a keyboard-driven browser, so we have to look elsewhere.&lt;/p&gt;

&lt;p&gt;Even though keyboard-obsessed users like me really are a small population, it usually corresponds to a tenacious community of programmers, which explains the &lt;strong&gt;relative abundance of keyboard-oriented browsers&lt;/strong&gt;: aside from Qutebrowser, we should also mention Surf, Nyxt (before called Next), forks of old Firefox like PaleMoon with the proper extensions and many others.&lt;/p&gt;

&lt;p&gt;Unfortunately, the quantity doesn't automatically means plenty of choices because those browsers usually have some limitations in one sense or another. Among those, Qutebrowser seems like the most solid one to me. Qutebrowser has a series of strong points: it runs on PyQt5, which means it uses QtWebEngine, which is, in turn, based on Chromium. Being tied to using Chromium's engine is the thing that makes me itchy the most, and yet I have to admit it increases Qutebrowser compatibility with a lot of services while making it reliable from a security point of view too.&lt;/p&gt;

&lt;p&gt;Thanks to the dedication of the author and maintainer, Florian Bruhin, and to a small but unresting community of developers and users, &lt;strong&gt;Qutebrowser already covers the principal needs&lt;/strong&gt;: themes, bookmarks, dark mode, a good adblocking based on Brave Browser's Rust libraries too. Despite all that, you cannot expect an instant solution for every not-so-common problem like it happens with Chromium and Firefox extensions. In those cases, you don't have an alternative to working by coding. And that's where my actual journey with Qutebrowser begins. In this regard, see SwapForQute, my first userscript for Qutebrowser.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;SwapForQute&lt;/strong&gt; (or just SFQ) is an userscript for qutebrowser that replaces your URLs with new ones following an easy to set configuration.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For a detailed explanation of how to install it, how it works, how to build keybindings and so on, &lt;a href="https://github.com/gicrisf/swapforqute" rel="noopener noreferrer"&gt;check the repo on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want to go deeper in the rabbit hole, &lt;a href="https://github.com/gicrisf/qute-config" rel="noopener noreferrer"&gt;see my Qutebrowser literate configuration&lt;/a&gt; too.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;(Photo by Jay Zhang on Unsplash)&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>writing</category>
      <category>twilio</category>
      <category>php</category>
    </item>
    <item>
      <title>Get user inputs in Emacs Lisp</title>
      <dc:creator>Giovanni Crisalfi</dc:creator>
      <pubDate>Sun, 13 Nov 2022 19:44:23 +0000</pubDate>
      <link>https://forem.com/gicrisf/get-user-inputs-in-emacs-lisp-l6a</link>
      <guid>https://forem.com/gicrisf/get-user-inputs-in-emacs-lisp-l6a</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally posted on &lt;a href="https://www.zwitterio.it/en/software/get-user-inputs-in-emacs-lisp/" rel="noopener noreferrer"&gt;zwitterio.it&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This post is intended to give beginners (or anyone else) an introduction on how to use lisp to manage user's inputs on Emacs.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple interactive function
&lt;/h2&gt;

&lt;p&gt;Imagine you want to write a simple interactive function to cheer up an Emacs' user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;cheer-me-up&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;interactive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="s"&gt;"You're great, keep it up!"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;cheer-me-up&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everytime we launch this one, the same result will be showed in the minibuffer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="s"&gt;"You’re great, keep it up!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works, but it can be boring on the long run.&lt;br&gt;
Enhance the function by proposing a new catchphrase every time you launch it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;cheer-me-up&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;goodvibes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;interactive&lt;/span&gt; &lt;span class="s"&gt;"sEnter good vibes as string: "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="nv"&gt;goodvibes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way, the function will reflect your communication and optimism on yourself, by repeating exactly the expression you pass.&lt;/p&gt;

&lt;p&gt;Every time the function will be launched by the user, the minibuffer of Emacs will ask for good vibes.&lt;/p&gt;

&lt;p&gt;That's pretty basic, because we're just asking for a string and rethrowing it to the user without any check.&lt;/p&gt;

&lt;p&gt;But what if the user isn't writing good stuff on the first place?&lt;br&gt;
Maybe he needs this function precisely because it's all melancholical and gloomy.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demand good vibes
&lt;/h2&gt;

&lt;p&gt;We could introduce a check that excludes bad words from the input and replace it with good ones, but it wouldn't be easy.&lt;br&gt;
Doable, but not easy.&lt;br&gt;
Let's keep it simple for the moment: what if we ask the user something else?&lt;br&gt;
Not a phrase, for example, but a number?&lt;/p&gt;

&lt;p&gt;We could list a great number of positive phrases and ask the user for a number, which will be the index of the phrase in the list.&lt;br&gt;
In this case, the input should be an integer.&lt;br&gt;
How do we do that?&lt;/p&gt;

&lt;p&gt;First of all, let's declare a list named &lt;code&gt;goodvibes&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="c1"&gt;;; list of positive phrases&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;setq&lt;/span&gt; &lt;span class="nv"&gt;goodvibes&lt;/span&gt;
      &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"You're great!"&lt;/span&gt; &lt;span class="s"&gt;"Keep it up!"&lt;/span&gt; &lt;span class="s"&gt;"People loves you!"&lt;/span&gt;
        &lt;span class="c1"&gt;;; It's better to comment out the following ones&lt;/span&gt;
        &lt;span class="c1"&gt;;; "Shut up, nothing you say has value" "The world is an insane fabric of pain and we should destroy everything"&lt;/span&gt;
        &lt;span class="c1"&gt;;; Ok, we can keep the last one&lt;/span&gt;
        &lt;span class="s"&gt;"Nature is good, she's not a cruel mother and you must be happy to be alive. Of course."&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have a total of 4 elements in the list and we want to access them from the function.&lt;br&gt;
The function asks for an index, which should be an integer between 0 and 3.&lt;br&gt;
What if the user doesn't know anything about the list up here and asks for a fifth element?&lt;br&gt;
What if the user gives us a float number insted of an integer? Or, worst, what if the user writes off a numberless string?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;cheer-me-up&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;interactive&lt;/span&gt; &lt;span class="s"&gt;"sChoose a number, receive good vibes~ "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;;; We are asking for a string from the user, so this could be anything.&lt;/span&gt;
  &lt;span class="c1"&gt;;; We must be sure it's an integer.&lt;/span&gt;
  &lt;span class="nv"&gt;&amp;lt;&amp;lt;format-input-as-integer&amp;gt;&amp;gt;&lt;/span&gt;

  &lt;span class="c1"&gt;;; Isn't it possible to convert the string to an integer?&lt;/span&gt;
  &lt;span class="c1"&gt;;; Tell the user!&lt;/span&gt;
  &lt;span class="nv"&gt;&amp;lt;&amp;lt;signal-no-number-error&amp;gt;&amp;gt;&lt;/span&gt;

  &lt;span class="c1"&gt;;; Now we need to know the integer is not inferior to 0 or superior to 3&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;index&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nv"&gt;index&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="c1"&gt;;; if we pass the condition, good, print out your message&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;nth&lt;/span&gt; &lt;span class="nv"&gt;index&lt;/span&gt; &lt;span class="nv"&gt;goodvibes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c1"&gt;;; Else block:&lt;/span&gt;
    &lt;span class="c1"&gt;;; if the number exceeds the range from a way or another, tell the user.&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;&amp;lt;signal-out-of-range-error&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To manage those cases, we need to know two concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;How to format strings&lt;/strong&gt; (&lt;a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Formatting-Strings.html" rel="noopener noreferrer"&gt;here in the GNU docs&lt;/a&gt;);&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;How to signal errors&lt;/strong&gt; (&lt;a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Signaling-Errors.html" rel="noopener noreferrer"&gt;here in the GNU docs&lt;/a&gt;);&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You might say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;But reading documentation is bOrInG!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Don't worry, this time we will explore the topics practically here below; just follow me through the comments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Formatting strings
&lt;/h2&gt;

&lt;p&gt;Formatting as integer is pretty simple if you know how to format strings. From the docs:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Formatting means constructing a string by substituting computed values at various places in a constant string.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this case, we want to require that the value is of a particular type and it can be checked specifying a format. &lt;code&gt;%d&lt;/code&gt;, for example,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;replace the specification with the base-ten representation of a signed integer. The object can also be a floating-point number that is formatted as an integer, dropping any fraction.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, if the user gives us &lt;code&gt;1&lt;/code&gt;, we're fine. And what if the user gives us &lt;code&gt;1.0&lt;/code&gt;? We're fine too! The floating point will be converted automatically to the closest integer. This means that &lt;code&gt;1.1&lt;/code&gt; or &lt;code&gt;1.2&lt;/code&gt; will be both converted to &lt;code&gt;1&lt;/code&gt; too. In other context, you could manage those cases differently, because if the user is giving you floating points, maybe the decimal means something and you want to tell them something's wrong. This case is simple, so we will be chill about that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="c1"&gt;;; assign the parsed value to the index var itself;&lt;/span&gt;
&lt;span class="c1"&gt;;; we're not going to need the raw input anymore;&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;setq&lt;/span&gt; &lt;span class="nv"&gt;index&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="s"&gt;"%d"&lt;/span&gt; &lt;span class="nv"&gt;index&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course, if the user gives us something that cannot be in any way interpreted as an integer, we have to tell them. To be honest, this part is already managed by the &lt;code&gt;format&lt;/code&gt; command itself.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you supply a value that doesn’t fit the requirements, an error is signaled.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, we don't have to implement this code by ourselves:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="c1"&gt;;; If user's input is not a number, the `format` function will tell them;&lt;/span&gt;
&lt;span class="c1"&gt;;; We can proceed with our function.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Throwing an error
&lt;/h2&gt;

&lt;p&gt;We still cannot vibe with our function results because another block, the last one, is missing.&lt;/p&gt;

&lt;p&gt;What if the index is not in the desired range?&lt;br&gt;
Of course, an error would be displayed this time too, but we need to explain clearly to the user what they have to do to not triggering it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Signaling an error means beginning error processing&lt;/strong&gt;. Error processing normally aborts all or part of the running program and returns to a point that is set up to handle the error (see &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Processing-of-Errors.html" rel="noopener noreferrer"&gt;How Emacs Processes Errors&lt;/a&gt;).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Errors are managed by &lt;code&gt;error&lt;/code&gt; function (what a shock, isn't it?). A typical use of error from the docs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;error&lt;/span&gt; &lt;span class="s"&gt;"That is an error -- try something else"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="nb"&gt;error&lt;/span&gt;&lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;That&lt;/span&gt; &lt;span class="nv"&gt;is&lt;/span&gt; &lt;span class="nv"&gt;an&lt;/span&gt; &lt;span class="nb"&gt;error&lt;/span&gt; &lt;span class="nv"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;try&lt;/span&gt; &lt;span class="nv"&gt;something&lt;/span&gt; &lt;span class="nv"&gt;else&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We take inspiration for our use-case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;error&lt;/span&gt; &lt;span class="s"&gt;"Out of range error -- choose a number between 0 and 3."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.reddit.com/r/emacs/comments/yubhff/comment/iw8pms1/?utm_source=share&amp;amp;utm_medium=web2x&amp;amp;context=3" rel="noopener noreferrer"&gt;As &lt;em&gt;nv-elisp&lt;/em&gt; points out on Reddit&lt;/a&gt;,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When using message you'll want to provide the format-string argument. Otherwise the user's strings will fail if they include format specifiers. e.g. if your goodvibes list includes "%dont mind me%", the %d will be interpreted as part of the format-string argument and will throw an error.&lt;/p&gt;

&lt;p&gt;For an error caused by user input, it's better to use user-error. This will prevent the debugger from being hit any time a user inputs an out of range number.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, in this case it would be more advisable to write &lt;code&gt;user-error&lt;/code&gt; instead of simply &lt;code&gt;error&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;user-error&lt;/span&gt; &lt;span class="s"&gt;"Out of range error -- choose a number between 0 and 3."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;We learned how to use &lt;code&gt;format&lt;/code&gt; and &lt;code&gt;error&lt;/code&gt;, two fundamental functions to manage user's inputs or for writing lisp for Emacs in general.&lt;br&gt;
In particular, we should remember that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;error&lt;/code&gt; stops the process and throws up the specified error to the user;&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;format&lt;/code&gt; can have various specifications like:

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;%s&lt;/code&gt;, for common strings;&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;%d&lt;/code&gt;, for integers;&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;%f&lt;/code&gt;, for "floats".&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To be absolutely correct, I should remember you that there's not a single type of integers or floats or strings; for example, strictly abiding to &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Formatting-Strings.html#index-format_002dmessage" rel="noopener noreferrer"&gt;what the docs are saying&lt;/a&gt;, &lt;code&gt;%f&lt;/code&gt; refers to "the decimal-point notation for a floating-point number"; you can store floats in other ways, if you need.&lt;/p&gt;

&lt;p&gt;Coming back to our function, that's how it finally appears:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;cheer-me-up&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;interactive&lt;/span&gt; &lt;span class="s"&gt;"sChoose a number, receive good vibes~ "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;;; We are asking for a string from the user, so this could be anything.&lt;/span&gt;
  &lt;span class="c1"&gt;;; We must be sure it's an integer.&lt;/span&gt;
  &lt;span class="c1"&gt;;; assign the parsed value to the index var itself;&lt;/span&gt;
  &lt;span class="c1"&gt;;; we're not going to need the raw input anymore;&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;setq&lt;/span&gt; &lt;span class="nv"&gt;index&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="s"&gt;"%d"&lt;/span&gt; &lt;span class="nv"&gt;index&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="c1"&gt;;; Isn't it possible to convert the string to an integer?&lt;/span&gt;
  &lt;span class="c1"&gt;;; Tell the user!&lt;/span&gt;
  &lt;span class="c1"&gt;;; If user's input is not a number, the `format` function will tell them;&lt;/span&gt;
  &lt;span class="c1"&gt;;; We can proceed with our function.&lt;/span&gt;

  &lt;span class="c1"&gt;;; Now we need to know the integer is not inferior to 0 or superior to 3&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;index&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nv"&gt;index&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="c1"&gt;;; if we pass the condition, good, print out your message&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;nth&lt;/span&gt; &lt;span class="nv"&gt;index&lt;/span&gt; &lt;span class="nv"&gt;goodvibes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c1"&gt;;; Else block:&lt;/span&gt;
    &lt;span class="c1"&gt;;; if the number exceeds the range from a way or another, tell the user.&lt;/span&gt;
    &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;user-error&lt;/span&gt; &lt;span class="s"&gt;"Out of range error -- choose a number between 0 and 3."&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the moment for a confession: in order to teach how the &lt;code&gt;format&lt;/code&gt; function works, I somehow have disguised you. The &lt;code&gt;interactive&lt;/code&gt; function, in fact, gives the chance to filter the values in place! That's what the "s" before "Choose" is there for (see &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Interactive-Codes.html#Interactive-Codes" rel="noopener noreferrer"&gt;Code Characters for interactive in the docs&lt;/a&gt; to learn more)&lt;br&gt;
Knowing this, we could write everything more concisely, like that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;cheer-me-up&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;interactive&lt;/span&gt; &lt;span class="s"&gt;"nChoose a number, take good vibes~ "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;index&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nv"&gt;index&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;nth&lt;/span&gt; &lt;span class="nv"&gt;index&lt;/span&gt; &lt;span class="nv"&gt;goodvibes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;user-error&lt;/span&gt; &lt;span class="s"&gt;"Out of range error -- choose a number between 0 and 3."&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I started with the "n" character, because it means you want to take a &lt;strong&gt;number&lt;/strong&gt; as an argument.&lt;br&gt;
The only difference from before is that this new function lacks the previous tolerance for floats, but I don't see this as a flaw.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Watch out!&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;format&lt;/code&gt; specifications and &lt;code&gt;interactive&lt;/code&gt; first character's meanings are not exactly superimposable!&lt;br&gt;
Using "f" in &lt;code&gt;interactive&lt;/code&gt; means asking for a file, not for a float.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Keep it up!
&lt;/h2&gt;

&lt;p&gt;Hoping this post was an enjoyable passage in your path through lispian parenthesis, I wish you good luck for your journey.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;nth&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nv"&gt;goodvibes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;;; =&amp;gt; "Keep it up!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(and a big thanks to &lt;a href="https://mastodon.online/@arialdo" rel="noopener noreferrer"&gt;Arialdo&lt;/a&gt; for his help in tracking down my typos)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>emacs</category>
      <category>lisp</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
